EF Core 5 or 6, how to select all related products based on mutual categories - performance

I have a many-to-many relationship between Products and Categories, I want to select all the related products (by any mutual categories) in EF Core 5, 6.
I can add all the products by each mutual category by looping on product categories and then distinct them, but this is not a nice solution at all, I am sure there is a single EF Linq query for this job.
Relation is like this:
Products - ProductInCategories - Categories
var allProducts = Product.ProductInCategories
.SelectMany(t => t.Category.ProductInCategories)
.Select(t => t.Product)
.Distinct().ToList();
This is neither nice nor efficient, please let me know the best solution.

For select all the products with related categories:
var allProducts = _context.Product.Include(a => a.ProductInCategories)
.ThenInclude(a => a.Category)
.ToList();
For select all the products with specific category(e.g. which category id equals to 1):
var allProducts = _context.Product.Include(a => a.ProductInCategories)
.ThenInclude(a => a.Category)
.Where(a => a.ProductInCategories.Any(c => c.CategoryId == 1))
.ToList();
If you just want to select the specific products without displaying the related category:
var allProducts = _context.Product
.Where(a => a.ProductInCategories.Any(c => c.CategoryId == 1))
.ToList();

Related

Where in clause using linq

trying to convert a query which has 2 levels of where in clauses to linq and getting some errors. Can anybody help me on this?
Original Query:
select id
from student
where suId
in (select suId
from subjects
where cid
in (select id
from chapters
where chapter='C203'))
LINQ query:
var query = (from s in dc.students
let subs = (from su in dc.subjects
where su.cid == Convert.ToInt32(from c in dc.Chapters
where c.chapter == 'Ç203'
select c.id) //Single chapter id will be returned
select su.suid)
where subs.Contains(s.sid)
select s.id).ToArray();
Am getting below 2 errors while compiling app
'System.Linq.IQueryable' does not contain a definition for 'Contains' and the best extension method overload 'System.Linq.ParallelEnumerable.Contains(System.Linq.ParallelQuery, TSource)' has some invalid arguments
Instance argument: cannot convert from 'System.Linq.IQueryable' to 'System.Linq.ParallelQuery'
Since Linq is lazy-loading everything you don't need to cram everything into a single statement; you can do something like this:
var chapterIds = dc.Chapters
.Where(c => c.Chapter == "C023")
.Select(c => c.Id);
var subjectIds = dc.Subjects
.Where(s => chapterIds.Contains(s.Cid))
.Select(s => s.Suid);
var students = dc.Students
.Where(s => subjectIds.Contains(s.Suid))
.Select(s => s.Sid)
.ToArray();
This way you can debug each subquery by looking at what it returns.
However, looking at your original select you can rewrite the whole thing as a Join and get rid of the bugging issue:
var students = dc.Chapters.Where(c => c.Chapter == "C023")
.Join(dc.Subjects,
c => c.Id,
s => s.Cid,
(chapter, subject) => subject)
.Join(dc.Students,
subj => subj.Suid,
student => student.Suid,
(subj, st) => st.Sid)
.ToArray();

LINQ to Entities - query across relationships and filter

I am having a very difficult time with querying a set of related entities with LINQ and Lambda expressions.
I have four entities that are related as such ...
Vehicles 1:n VehicleTypes n:1 Prices 1:n CustomerTypes
I am trying to obtain a list of Prices for a given Vehicle and CustomerType. For example I would like to obtain all the Prices for a Ford Mustang (VehicleTypeId = 2). In those Prices I would like included the CustomerType (Government, Commercial, Retail) that the Price pertains to.
I thought I might be able to do the following ...
Prices.Include(p => p.VehicleTypes)
.Include(p => p.CustomerTypes)
.Where(p => p.VehicleTypes.Vehicles.Select(v => v.Id == 2)
However I get this error ...
Cannot implicitly convert type 'System.Collections.Generic.IEnumerable<bool>' to 'bool'
I cannot seem to create a Where condition where I can filter the Id of the Vehicle to be purchased yet also include the CustomerType in the results.
EDIT: Just wanted to note that I have included using System.Data.Entity so I have access to the type safe Include extension
If you need the prices to that kind of vehicle and an specific customer type, you can filter as follows:
var prices= Prices.Include(p => p.VehicleTypes)
.Include(p => p.CustomerTypes)
.Where(p => p.VehicleTypes.Vehicles.Any(v => v.Id == 2)// With this condition you make sure that Mustang belong to this set of vehicles
&& p.CustomerTypes.Type=="Commercial");
But in case you want to filter the vehicles in the result, you are going to need to project your query to an anonymous type or a DTO:
var query= Prices.Include(p => p.VehicleTypes)
.Include(p => p.CustomerTypes)
.Where(p => p.VehicleTypes.Vehicles.Any(v => v.Id == 2)
&& p.CustomerTypes.Type=="Commercial")
.Select(p=>new {CustomerType=p.CustomerTypes.Type,
Vehicles=p.VehicleTypes.Vehicles.Where(v => v.Id == 2)});

Use Linq to find records with missing related data

I am trying to use Linq to Entities to find the categories where no orders exist.
TABLE Customer TABLE Order
------------------ ----------------
CustId Category OrderId FKCustId
1 2 1 1
2 2
3 3
This is a classic 1 to many Customer/Order relationship. Given this data, only category 3 has no orders associated with it, so I want to generate a result set with category 3 as the only item. This must be a straightforward query to write, but I have not been able to figure it out. I've tried a ton of different angles; here is one that didn't work:
var dtos = ctx.Customers
.GroupBy(c => c.Category)
.Where(c => !c.Any(c2 => !c2.Orders.Any()))
.Select(c => c.Key);
When I tried this, it returned a category that does have orders while not returning the category missing orders.
Thanks in advance for any help!
Roger
Your query is close, but the !Any followed by !Any is throwing off your logic. You want to select categories where all customers have no orders. But your query selects all categories where no customer has no order. I hope that made sense
Try changing your first !Any to All:
var dtos = ctx.Customers
.GroupBy(c => c.Category)
.Where(c => c.All(c2 => !c2.Orders.Any()))
.Select(c => c.Key);
or in query syntax:
var dtos =
from c in Customers
group c by c.Category into g
where g.All(c => !c.Orders.Any())
select g.Key;
Alternatively change the second !Any to Any.
var dtos = ctx.Customers
.GroupBy(c => c.Category)
.Where(c => !c.Any(c2 => c2.Orders.Any()))
.Select(c => c.Key);
or in query syntax:
var dtos =
from c in Customers
group c by c.Category into g
where !g.Any(c => c.Orders.Any())
select g.Key;
You first need to get the customers who do not have any orders and then get the categories. Sort of do your where first and then get categories out

Entity Framework Take N items of child collection

Say I have a Customer entity, and a Sales entity, of 1-to-many relationship.
How could I get all Customers with N number of most recent sales?
var result = Customers.Where(c => c.Sales.Any());
This would return all customers with ALL their sales.
What if I want just 2 sales record from each customer?
P/S: I can do that with query syntax, i'm looking for method syntax solution. I just can't figure out how to chain them together in method syntax form
var result = from cust in context.Customers
select new
{
Customers = cust,
Sales = cust.Sales.OrderBy(s => s.Date).Take(2)
};
This works, but i'm not sure if this is the best way to do it.
EDIT:
OK, it turns out the query syntax that i included here is not working too.
Only the Sales in the anonymous type is effectively reduced to 2 records.
var filtered = result.AsEnumerable().Select(r => r.Customers);
doing this will still result in a list of customers with ALL their sales
You can do a project as described in here
var dbquery = Customers.Select( c => new {
Customer = c,
Sales = c.Sales.OrderBy(s => s.Date)
.Take(2).Select( s => new { s, s.SalesDetails})
});
var customers = dbquery
.AsEnumerable()
.Select(c => c.Customer);

What's the LINQ to select the latest item from a number of versioned items?

I've got a class like the following:
public class Invoice
{
public int InvoiceId {get;set;}
public int VersionId {get;set;}
}
Each time an Invoice is modified, the VersionId gets incremented, but the InvoiceId remains the same. So given an IEnumerable<Invoice> which has the following results:
InvoiceId VersionId
1 1
1 2
1 3
2 1
2 2
How can I get just the results:
InvoiceId VersionId
1 3
2 2
I.e. I want just the Invoices from the results which have the latest VersionId. I can easily do this in T-SQL, but cannot for the life of me work out the correct LINQ syntax. I'm using Entity Framework 4 Code First.
Order by the VersionId, group them by InvoiceId, then take the first result of each group. Try this:
var query = list.OrderByDescending(i => i.VersionId)
.GroupBy(i => i.InvoiceId)
.Select(g => g.First());
EDIT: how about this approach using Max?
var query = list.GroupBy(i => i.InvoiceId)
.Select(g => g.Single(i => i.VersionId == g.Max(o => o.VersionId)));
Try using FirstOrDefault or SingleOrDefault in place of Single as well... it would give the same result although Single shows the intention better.
EDIT: I've tested both these queries with LINQ to Entities. They seem to work, so perhaps the issue is something else?
Option 1:
var latestInvoices = invoices.GroupBy(i => i.InvoiceId)
.Select(group => group.OrderByDescending(i => i.VersionId)
.FirstOrDefault());
EDIT: Changed 'Last' to 'FirstOrDefault', LINQ to Entities has issues with the 'Last' query operator.
Option 2:
var invoices = from invoice in dc.Invoices
group invoice by invoice.InvoiceId into invoiceGroup
let maxVersion = invoiceGroup.Max(i => i.VersionId)
from candidate in invoiceGroup
where candidate.VersionId == maxVersion
select candidate;
My version:
var h = from i in Invoices
group i.VersionId by i.InvoiceId into grouping
select new {InvoiceId = grouping.Key, VersionId = grouping.Max()};
Update
As was mentioned by Ahmad in the comments, the above query will return a projection. The version below will return a IQueryable<Invoice>. I use composition to build the query because I think it is more clear.
var maxVersions = from i in Invoices
group i.VersionId by i.InvoiceId into grouping
select new {InvoiceId = grouping.Key,
VersionId = grouping.Max()};
var latestInvoices = from i in Invoices
join m in maxVersions
on new {i.InvoiceId, i.VersionId} equals
new {m.InvoiceId, m.VersionId}
select i;

Resources