I have a query to search in single table where records are at Parent, Child Relationship like below
Table
Id
ParentId
Name
Status
so my query is like
var projects = from p in _projectSetupRepository.Table
from all in _projectSetupRepository.Table
where p.Status == status && p.ParentId == null &&
all.Status == status && ((all.ParentId == null && all.Id == p.Id) || all.ParentId == p.Id)
select p;
if (!string.IsNullOrEmpty(search))
projects = projects.Where(c => c.Name.Contains(search)).OrderBy(c => c.Name);
but I don't get actual results of parents if search with the child's name. What was the issue in the query?
PS
table contains thousands of data and performance is very important
PS
public class ProjectSetup
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Name { get; set; }
public bool Status { get; set; }
public ProjectSetup Project{ get; set; }
public virtual ICollection<ProjectSetup> SubProjects { get; set; }
}
Id
ParentId
Name
Status
1
null
P1
true
2
1
T1
true
3
1
T2
true
4
3
Q1
true
You can find all parents with specific child name(this query searches Level1 childs only):
var status = true;
var search = "T";
var projects = (from parent in context.Projects
join child in context.Projects on parent.Id equals child.ParentId into joinedT
from pd in joinedT.DefaultIfEmpty()
where parent.Status == status
&& parent.ParentId == null //filter top level parents only?
&& pd.Name.Contains(search)
select parent).Distinct().OrderBy(c => c.Name);
foreach(var p in projects)
{
Console.WriteLine(p.Id+":"+p.Name);
}
Console.WriteLine("Found results:"+ projects.Count());
here's fiddle: https://dotnetfiddle.net/PaCidA
If you are looking for multi-level solution I suggest you to take a look at hierarchyid data type and its usage in EF LINQ
https://softwarehut.com/blog/tech/hierarchyid-entity-framework
https://kariera.future-processing.pl/blog/hierarchy-in-the-entity-framework-6-with-the-hierarchyid-type/
Check for performance the folloging algorithm. Idea that we can Include several Project and check on the client what to load again. Algorithm uses EF Core navigation properties fixup.
var query = in_projectSetupRepository.Table
.Include(p => p.Project.Project.Project) // you can increase count of loaded parents
.Where(p => p.Status == status)
.AsQueryable();
var loadedDictionary = new Dictionary<int, ProjectSetup>>();
var projects = query;
if (!string.IsNullOrEmpty(search))
projects = projects.Where(c => c.Name.Contains(search));
while (true)
{
var loaded = projects.ToList();
// collect loaded with parents
foreach(var p in loaded)
{
var current = p;
do
{
if (!loadedDictionary.ContainsKey(current.Id))
loadedDictionary.Add(current.Id, current);
current = current.Project;
}
while (current != null)
}
// collect Ids of incomplete
var idsToLoad = loadedDictionary.Values
.Where(x => x.ParentId != null && x.Project == null)
.Select(x => x.ParentId.Value)
.ToList();
// everything is ok
if (idsToLoad.Count == 0)
break;
// make query to load another portion of objects
projects = query.Where(p => idsToLoad.Contains(p.Id));
}
var result = loadedDictionary.Values
.OrderBy(c => c.Name)
.ToList();
Related
I am trying to find all invoices to buyers, searching by buyer name (contains and equals filter). Looking for the cleanest way to do it.
I have a list of Buyers.
List <Buyer> AllBuyers;
And a Buyer is:
public class Buyer
{
public string BuyerIdentifier{ get; set; }
public string Name { get; set; }
}
I have a list of Invoices to buyers.
List <Invoice> AllInvoices;
And an Invoice is
public class Invoice
{
public string InvoiceID { get; set; }
public string BuyerID { get; set; }
public string Amount{ get; set; }
}
What I am doing currently:
List<string> BuyerIDs = new List<string> { };
foreach (Invoice inv in AllInvoices)
{
if (!(BuyerIDs.Contains(inv.BuyerID)))
{
// add BuyerID to list if it's not already there. Getting id's that are present on invoices and whose Buyer names match using contains or equals
BuyerIDs.Add(AllBuyers.First(b => b.BuyerIdentifier == inv.BuyerID
&& (b.Name.IndexOf(SearchValue, StringComparison.OrdinalIgnoreCase) >= 0)).BuyerIdentifier);
}
}
Invoices = AllInvoices.FindAll(i=> BuyerIDs.Contains(i.BuyerID));
LINQ query syntax is a little easier for me to understand than LINQ methods to join. So after replies below I am now doing this:
Invoices = (from buyer in AllBuyers
join invoice in AllInvoices on buyer.BuyerIdentifier equals invoice.BuyerID
where buyer.Name.IndexOf(SearchValue, StringComparison.OrdinalIgnoreCase) >= 0
select invoice).ToList();
If all you need are the invoices, you could join your two collections, filter, and select the invoices
AllBuyers.Join(AllInvoices,
a => a.BuyerIdentifier,
a => a.BuyerID,
(b, i) => new { Buyer = b, Invoice = i })
.Where(a => a.Buyer.Name.Contains("name"))
.Select(a => a.Invoice).ToList();
If you want the buyers as well, just leave out the .Select(a => a.Invoice).
The Contains method of a string will match an equals as well.
Here is a suggestion where I create a dictionary with BuyerIdentifier as keys and a List of Invoices as values:
var dict = AllBuyers.ToDictionary(k => k.BuyerIdentifier,
v => AllInvoices.Where(i => i.BuyerID == v.BuyerIdentifier).ToList());
Then you can access a list of Invoices for a specific buyer like so:
List<Invoice> buyerInvoices = dict[buyerId];
This should work for you:
var InvoiceGrouping = AllInvoices.GroupBy(invoice => invoice.BuyerID)
.Where(grouping => AllBuyers.Any(buyer => buyer.BuyerIdentifier == grouping.Key && buyer.Name.IndexOf(pair.Value, StringComparison.OrdinalIgnoreCase) >= 0));
What you end up with is a grouping which has a buyer's ID as the key and all their invoices as the value.
If you want just a flat list of invoices, you can do like so:
var Invoices = AllInvoices.GroupBy(invoice => invoice.BuyerID)
.Where(grouping => AllBuyers.Any(buyer => buyer.BuyerIdentifier == grouping.Key && buyer.Name.IndexOf(pair.Value, StringComparison.OrdinalIgnoreCase) >= 0))
.SelectMany(grouping => grouping);
Note the added SelectMany at the end which, since IGrouping implements IEnumerable, flattens the groupings into a single enumeration of values.
As an ILookup fanboy, this would be my approach:
var buyerMap = AllBuyers
.Where(b => b.Name.IndexOf(SearchValue, StringComparison.OrdinalIgnoreCase) >= 0)
.ToDictionary(b => b.BuyerIdentifier);
var invoiceLookup = AllInvoices
.Where(i => buyerMap.ContainsKey(i.BuyerID))
.ToLookup(x => x.BuyerID);
foreach (var invoiceGroup in invoiceLookup)
{
var buyerId = invoiceGroup.Key;
var buyer = buyerMap[buyerId];
var invoicesForBuyer = invoiceGroup.ToList();
// Do your stuff with buyer and invoicesForBuyer
}
I have 2 tables Orders and Items and I am trying to query these 2 tables for statistics retrieval.
Orders has columns OrderID[PK], ItemID[FK], OrderStatus etc.
Items has columns ItemID[PK], ItemName, ItemPrice etc.
I am fetching list of orders based on date range and then I am returning their counts based on their status.
Below is my StatisticsResponse.cs to return the response.
public class StatisticsResponse
{
public int CancelledOrderCount { get; set; }
public int CompletedOrderCount { get; set; }
public int InProgressOrderCount { get; set; }
public int TotalOrders { get; set; }
public Dictionary<string,int> ItemOrders { get; set;}
}
This is how I am retrieving Orders between 2 dates.
var orders = _unitOfWork.OrderRepository
.GetMany(x => (x.OrderStatus == "Pending"
&& x.OrderDate.Value.Date >= dtStartDate
&& x.OrderDate.Value.Date < dtEndDate) ||
((x.OrderStatus == "Completed" || x.OrderStatus == "Cancelled")
&& x.DeliveryDate.Date >= dtStartDate || x.DeliveryDate.Date < dtEndDate) || (x.LastUpdated.Value.Date >= dtStartDate || x.LastUpdated.Value.Date < dtEndDate)).ToList();
if (orders != null)
{
return new StatisticsResponse()
{
TotalOrders = orders.Count(),
CancelledOrderCount = orders.Where(x => x.OrderStatus == "Cancelled").Count(),
CompletedOrderCount = orders.Where(x => x.OrderStatus == "Completed").Count(),
InProgressOrderCount = orders.Where(x => x.OrderStatus != "Completed" && x.OrderStatus != "Cancelled").Count()
}
}
Now, in the ItemOrders property, which is of type Dictionary<string,int>, I want to group each item with their name and count. I have ItemID in my orders list, and I would like to join 2 tables to get the name before storing.
I have tried to use GroupBy as below but am totally stuck on how to get the name for the Item after grouping
ItemOrders = new Dictionary<string, int>
{
orders.GroupBy(x=>x.ItemID)// Stuck here
}
I also read about GroupJoin but couldn't quite make sure whether it can fit in here.
Could someone please let me know how I can join these 2 tables to get their name based on their ID?
You can use something along this:
using System.Entity.Data; //to use Include()
...
Dictionary<string,int> itemOrders = dbContext.Orders.Include(o=> o.Item)
.GroupBy(o=> o.Item)
.ToDictionary(g => g.Key.Name, g => g.Count());
This is assuming:
There is a navigation property set up from Order to Item
Each Order has one Item
So, I was able to achieve this with GroupJoin through various online example as below:
if (orders != null)
{
var items = _unitOfWork.ItemRepository.GetAll();
var itemOrders = items.GroupJoin(orders,
item => item.ItemID,
ord => ord.ItemID,
(item, ordrs) => new
{
Orders = ordrs,
itemName = item.ItemName
});
StatisticsResponse statsResponse = new StatisticsResponse()
{
//...
};
foreach (var item in itemOrders)
{
statsResponse.ItemOrders.Add(item.itemName, item.Orders.Count());
}
return statsResponse;
}
I think I'm misunderstanding something about EF Includes or lazy-loading or perhaps I haven't set my Entities up correctly.
If I have this query, it works as expected. I get a list of products with their associated productOptions
var prodQuery = db.Products
.Include("ProductOptions")
.AsNoTracking()
.Where(p =>
p.CategoryId == category.Id
&& p.Active && !p.Deleted
&& p.ProductOptions.Any(po => po.Active && !po.Deleted)
).ToList();
However, when I try to select them to DTO's... The products contain no ProductOptions. Somehow they're not being included
var products = db.Products
.Include("ProductOptions")
.AsNoTracking()
.Where(p =>
p.CategoryId == category.Id
&& p.Active && !p.Deleted
&& p.ProductOptions.Any(po => po.Active && !po.Deleted))
.Select(p =>
new ProductDTO
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
ProductOptionDTOs = p.ProductOptions
.Where(po => po.Active && !po.Deleted)
.Select(po =>
new ProductOptionDTO
{
Id = po.Id,
Name = po.Name,
Price = po.Price
}
).ToList()
}
).ToList();
Here are my Entities... Removed non-relevant properties for brevity
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProductOption> ProductOptions { get; set; }
}
public class ProductOption
{
public int Id { get; set; }
public string Name { get; set; }
public int ProductId { get; set; }
public Product Product { get; set; }
}
Can someone help me understand I'm missing ProductOptions in my second query?
My mistake everyone, no longer a problem. Thanks for all your help
After investigating everyone's suggestions and going through my code I found I had made a mistake in my ProductDTO (the declaration for the ProductOptionDTOs property) that was silently failing and causing ProductOptionDTOs to be null without errors.
I got suspicious of my DTO after I tested the sql being generated by EF, and found the sql was correctly returning the right data, therefore I presumed there must have been a problem mapping it back to the DTO.
So it turns out it would have been critical to show you guys my DTO's in the first place even though I dismissed that idea
Fixed :)
You have
public ICollection<ProductOption> ProductOptions { get; set; }
ProductOptions = p.ProductOptions
.Where(po => po.Active && !po.Deleted)
.Select(po =>
new ProductOptionDTO
{
Id = po.Id,
Name = po.Name,
Price = po.Price
}).ToList()
Notice the difference with
new ProductOptionDTO
and the type of the ProductOptions member in your class
ICollection<ProductOption>
so try changing
public ICollection<ProductOption> ProductOptions { get; set; }
public ICollection<ProductOptionDTO> ProductOptions { get; set; }
and change the name of your 'ProductOption' class to 'ProductOptionDTO':
public class ProductOption
to
public class ProductOptionDTO
also shouldn't your 'Product' class be changed as follows?
public class Product
be
public class ProductDTO
Edit: Try this query expression if only as a test...
var products = (from p in db.Products
where p.CategoryId == category.Id
&& p.Active && !p.Deleted
&& p.ProductOptions.Any(po => po.Active && !po.Deleted)
select new ProductDTO
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
ProductOptionDTOs = (from po in p.ProductOptions
where po.Active
&& !po.Deleted
select new ProductOptionDTO
{
Id = po.Id,
Name = po.Name,
Price = po.Price
}).ToList()
}).ToList()
EDIT2: If the above doesn't work then try this where you specify the joining properties...
var products = (from p in db.Products
where p.CategoryId == category.Id
&& p.Active && !p.Deleted
&& p.ProductOptions.Any(po => po.Active && !po.Deleted)
select new ProductDTO
{
Id = p.Id,
Name = p.Name,
Description = p.Description,
ProductOptionDTOs = (from po in db.ProductOptions
where po.ProductId == p.ProductId
&& po.Active
&& !po.Deleted
select new ProductOptionDTO
{
Id = po.Id,
Name = po.Name,
Price = po.Price
}).ToList()
}).ToList()
The problem is that Include has no effect when executing complex query.
One way to fix this is to use so-called projection. It's just a dynamic type that is used within query (new {...}).
Try something like this:
var products = db.Products
.AsNoTracking()
.Where(p =>
p.CategoryId == category.Id
&& p.Active && !p.Deleted
&& p.ProductOptions.Any(po => po.Active && !po.Deleted))
.Select(p =>
new
{
product = p,
options = p.ProductOptions.Where(po => po.Active && !po.Deleted)
}
)
.AsEnumerable()
.Select(s =>
new ProductDTO
{
Id = s.product.Id,
Name = s.product.Name,
Description = s.product.Description,
ProductOptionDTOs = s.options
.Select(po =>
new ProductOptionDTO
{
Id = po.Id,
Name = po.Name,
Price = po.Price
}
)
}
);
Hello I have just started to try and understand Parallel Linq and with my first attempt I am having no success. I am using EF 4.0 and a repository pattern class that I created to query the data. I don't believe that the repository pattern is the problem but I could be mistaken.
The database that I have isn't setup the way that I would like it but hey I inherited the system. The code that I am having the problem with is below:
var gId = Sql.ToGuid(Request["ID"]);
var lOrdersGridList = new OrdersGridList(); //Class that only contains properties
var lOrdersForContact = new BaseRepository<ORDER>()
.Find(i => i.ORDERS_CONTACTS.Where(b => b.CONTACT_ID == gId).Count() > 0).AsParallel()
.Select(i =>
new OrdersGridList
{
ORDER_ID = i.ID,
ORDER_NUM = i.ORDER_NUM,
SHIPPING_ACCOUNT_ID = i.ORDERS_ACCOUNTS.Where(b => b.ORDER_ID == i.ID && b.ACCOUNT_ROLE == "Ship To").First().ACCOUNT_ID,
SHIPPING_ACCOUNT_NAME = i.ORDERS_ACCOUNTS.Where(b => b.ORDER_ID == i.ID && b.ACCOUNT_ROLE == "Ship To").First().ACCOUNT.NAME,
SHIPPING_CONTACT_ID = i.ORDERS_CONTACTS.Where(b => b.ORDER_ID == i.ID && b.CONTACT_ROLE == "Ship To").First().CONTACT_ID,
SHIPPING_CONTACT_NAME = i.ORDERS_CONTACTS.Where(b => b.ORDER_ID == i.ID && b.CONTACT_ROLE == "Ship To")
.Select(b => new { SHIPPING_CONTACT_NAME = (b.CONTACT.FIRST_NAME + ' ' + b.CONTACT.LAST_NAME) }).First().SHIPPING_CONTACT_NAME,
NAME = i.NAME
}).DefaultIfEmpty(lOrdersGridList).ToList<OrdersGridList>();
grdMain.DataSource = lOrdersForContact.ToDataTable().DefaultView; //ToDataTable extension function converts the List Object to a datatable.
If I run the Code without AsParallel the code executes with no problem however, once I add AsParallel I receive the following error:
Also just in case you wanted to see this is the class that I am declaring as new for the Select Above:
public class OrdersGridList : EntityObject
{
public string ORDER_NUM { get; set; }
public Guid ORDER_ID { get; set; }
public Guid SHIPPING_ACCOUNT_ID { get; set; }
public string SHIPPING_ACCOUNT_NAME { get; set; }
public Guid SHIPPING_CONTACT_ID { get; set; }
public string SHIPPING_CONTACT_NAME { get; set; }
public string NAME { get; set; }
}
If I remove all of the relationships that are used to retrieve data in the select I don't receive any errors:
var lOrdersForContact = new BaseRepository<ORDER>()
.Find(i => i.ORDERS_CONTACTS.Where(b => b.CONTACT_ID == gId).Count() > 0).AsParallel()
.Select(i =>
new OrdersGridList
{
ORDER_ID = i.ID,
ORDER_NUM = i.ORDER_NUM,
//SHIPPING_ACCOUNT_ID = i.ORDERS_ACCOUNTS.Where(b => b.ORDER_ID == i.ID && b.ACCOUNT_ROLE == "Ship To").First().ACCOUNT_ID,
//SHIPPING_ACCOUNT_NAME = i.ORDERS_ACCOUNTS.Where(b => b.ORDER_ID == i.ID && b.ACCOUNT_ROLE == "Ship To").First().ACCOUNT.NAME,
//SHIPPING_CONTACT_ID = i.ORDERS_CONTACTS.Where(b => b.ORDER_ID == i.ID && b.CONTACT_ROLE == "Ship To").First().CONTACT_ID,
//SHIPPING_CONTACT_NAME = i.ORDERS_CONTACTS.Where(b => b.ORDER_ID == i.ID && b.CONTACT_ROLE == "Ship To")
// .Select(b => new { SHIPPING_CONTACT_NAME = (b.CONTACT.FIRST_NAME + ' ' + b.CONTACT.LAST_NAME) }).First().SHIPPING_CONTACT_NAME,
NAME = i.NAME
}).DefaultIfEmpty(lOrdersGridList).ToList<OrdersGridList>();
I would be more than happy to give more information if required. Any help you can provide on using PLinq I would appreciate it.
To me it appears like the BaseRepository class creates some kind of LINQ to Entities query using the Find and Select parameters.
Using AsParellel is made for LINQ to Objects, where your code actually evaluates the expressions you pass. Other LINQ dialects, including LINQ to Entities, translate it into a different query language like SQL. The SQL server may do reasonable parallelisation itself.
Lets say we have a sql relationship that can be modeled like this using C# classes
public class Parent
{
public int ID { get; set; }
public List<Child> Children { get; set; }
}
public class Child
{
public int ID { get; set; }
public Parent Parent { get; set; }
public int Number { get; set; }
}
I also know that the parent only has two children. I would I find all the parents where the children both satisfy a different criteria? Say One Child has a number = 0 and the other Child has a number = 1
Goin' out on a limb here...
(from p in context.Parents
where p.Children.Count == 2
where p.Children.Any(c => c.Number == 0)
select p).Where(p => p.Children.Any(c => c.Number == 1));
from p in context.Parents
where p.Children.Count == 2 // sounds like you can skip this one
where p.Children.Any(c => c.Number == 0)
where p.Children.Any(c => c.Number == 1)
select p;
from o in context.Parents where o.Children.Count == 2 && o.Children.Any(x => x.Number == 0) && x.Children.Any(x => x.Number == 1) select o;