Use Linq to find records with missing related data - linq

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

Related

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

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();

Select two lists as one list in ASP.NET Core linq

I am trying to create a query with ASP.NET Core EF Core and Linq that would give me a List of users based on two different lists, something like this:
return await _context.Users
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Property)
.ThenInclude(p => p.PropertyTenantLeases)
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Owner)
.Where(u => u.Id == userID)
.Select(u => new List<User>()
{
u.PropertyTenantLeases.Select(ptl => ptl.Tenant).ToList()
u.PropertyOwners.Select(po => po.Owner).ToList()
}).FirstOrDefaultAsync();
The tables that are used in this query are connected in the following way:
Everything is fine with this query except for the Select, with the Select I am trying to achieve that it returns a list of all the tenants in the PropertyTenantLeases table which is a junction table togheter with all the Owners form the PropertyOwners junction table (both Tenant and Owner are IdentityUser classes. When I right this query like this I get the following error:
The best overloeaded Add method 'List<User>.Add(User)' for the collection initializer has some invalid arguments
and also
Argument 1: cannot convert from 'System.Collections.Generic.List<RosyMasterDBManagement.Models.User>' to 'RosyMasterDBManagement.Models.User'
Joining two list is called a union in Linq -- I believe that is what you want:
note: I still can't test this since you gave a picture of the data model instead of the code that would allow me to be certain of how to implement. expect the fields to be named incorrectly etc.
var ownerlist = _context.Users
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Owner)
.ToList();
var tenantlist = _context.Users
.Include(u => u.PropertyOwners)
.ThenInclude(po => po.Property)
.ThenInclude(p => p.PropertyTenantLeases)
.ThenInclude(po => po.Tenant)
.ToList();
return ownerlist.Union(tenantlist);
I don't believe you need await() since ToList() forces it to not be lazy. But I could be wrong about that.

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)});

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);

Entity Framework 4 - What is the syntax for joining 2 tables then paging them?

I have the following linq-to-entities query with 2 joined tables that I would like to add pagination to:
IQueryable<ProductInventory> data = from inventory in objContext.ProductInventory
join variant in objContext.Variants
on inventory.VariantId equals variant.id
where inventory.ProductId == productId
where inventory.StoreId == storeId
orderby variant.SortOrder
select inventory;
I realize I need to use the .Join() extension method and then call .OrderBy().Skip().Take() to do this, I am just gettting tripped up on the syntax of Join() and can't seem to find any examples (either online or in books).
NOTE: The reason I am joining the tables is to do the sorting. If there is a better way to sort based on a value in a related table than join, please include it in your answer.
2 Possible Solutions
I guess this one is just a matter of readability, but both of these will work and are semantically identical.
1
IQueryable<ProductInventory> data = objContext.ProductInventory
.Where(y => y.ProductId == productId)
.Where(y => y.StoreId == storeId)
.Join(objContext.Variants,
pi => pi.VariantId,
v => v.id,
(pi, v) => new { Inventory = pi, Variant = v })
.OrderBy(y => y.Variant.SortOrder)
.Skip(skip)
.Take(take)
.Select(x => x.Inventory);
2
var query = from inventory in objContext.ProductInventory
where inventory.ProductId == productId
where inventory.StoreId == storeId
join variant in objContext.Variants
on inventory.VariantId equals variant.id
orderby variant.SortOrder
select inventory;
var paged = query.Skip(skip).Take(take);
Kudos to Khumesh and Pravin for helping with this. Thanks to the rest for contributing.
Define the join in your mapping, and then use it. You really don't get anything by using the Join method - instead, use the Include method. It's much nicer.
var data = objContext.ProductInventory.Include("Variant")
.Where(i => i.ProductId == productId && i.StoreId == storeId)
.OrderBy(j => j.Variant.SortOrder)
.Skip(x)
.Take(y);
Add following line to your query
var pagedQuery = data.Skip(PageIndex * PageSize).Take(PageSize);
The data variable is IQueryable, so you can put add skip & take method on it. And if you have relationship between Product & Variant, you donot really require to have join explicitly, you can refer the variant something like this
IQueryable<ProductInventory> data =
from inventory in objContext.ProductInventory
where inventory.ProductId == productId && inventory.StoreId == storeId
orderby inventory.variant.SortOrder
select new()
{
property1 = inventory.Variant.VariantId,
//rest of the properties go here
}
pagedQuery = data.Skip(PageIndex * PageSize).Take(PageSize);
My answer here based on the answer that is marked as true
but here I add a new best practice of the code above
var data= (from c in db.Categorie.AsQueryable().Join(db.CategoryMap,
cat=> cat.CategoryId, catmap => catmap.ChildCategoryId,
cat, catmap) => new { Category = cat, CategoryMap = catmap })
select (c => c.Category)
this is the best practice to use the Linq to entity because when you add AsQueryable() to your code; system will converts a generic System.Collections.Generic.IEnumerable to a generic System.Linq.IQueryable which is better for .Net engine to build this query at run time
thank you Mr. Khumesh Kumawat
You would simply use your Skip(itemsInPage * pageNo).Take(itemsInPage) to do paging.

Resources