Linq Include just one object from a one to many relation - linq

I have two tables with a one to many relation.
One Order has many Products.
I want to take a list of Orders with the free Product in each (one per order).
I've tried something like this:
this.ObjectContext.ORDERS.Include("PRODUCTS").Where(e=>e.PRODUCTS.price == 0).OrderBy(e => e.Order);
But this is not working.
Is there any other approach??
Thanks in advance.

if PRODUCTS is a collection, you can use the Any() extension method to find out if any product is free (alternatively, the All() method to find out if all products are free):
this.ObjectContext.ORDERS
.Include("PRODUCTS")
.Where(o => o.PRODUCTS.Any(p => p.price == 0))
.OrderBy(e => e.Order);
Based on your clarification of the desired output (all orders with optional free product), you can use this query:
this.ObjectContext.ORDERS
.Include("PRODUCTS")
.Select(o => new {
Order = o,
FreeProduct = o.PRODUCTS.FirstOrDefault(p => p.price == 0)
});
It will return a sequence of an anomymous type containing the order, and the free product (or null if no free product exists).

Related

In LINQ expression how to get FirstOrDefault with better performance?

In our Xamarin Forms app this code:
return database.GetAllWithChildren<Review>(x => x.ProductId == prodId, true).OrderByDescending(x => x.ReviewId).FirstOrDefault();
is having performance issues and it takes more time the more reviews exist for that product.
With a product with 7 reviews it took about 17 seconds which is unacceptable.
How could I optimize the performance?
After all I don't need all the reviews info, just the latest one.
It seems that this retrieves all 7 reviews and then sorts them descending and then gets the first in the list.
Is there a way to get only that one with the max ID?
It seems using the GetAllWithChildren is the problem. This method recursively loads the info of all the reviews, not just the one. You are then performing the ordering and selection after all the data returned from the DB.
The solution would be to first filter and only then return the Review:
var review = database.Table<Review>().Where(x => x.Product == prodId ).
OrderByDescending( x => x.ReviewId ).FirstOrDefault();
database.GetChildren( review, true );
return element;
database.GetAllWithChildren<Review> returns a List<Review> of fully constructed reviews. This means that the more reviews a product has, the more reviews get discarded by FirstOrDefault() right after being constructed.
Try getting the max id first, then filter reviews on it:
// This assumes that ID is int. Change to another nullable type matching ReviewId
int? maxId = database.Reviews.Where(r => r.ProductId == prodId).Max((int?)r.ReviewId);
return database
.GetAllWithChildren<Review>(x => x.ProductId == prodId && x.ReviewId == maxId, true)
.SingleOrDefault();

How to get top 10 customers based on values of orders

I have a list of Customers who each have a list of Orders. Each Order has a list of LineItems.
I would like to write a LINQ query that would get me the top 10 customers based on order value (i.e. money spent) and not the total number of orders.
One customer could have 2 orders but could have spent £10,000, but another customer could have 100 orders, and only spent £500.
Right now, I have this which gets me the top 10 customers by the number of orders.
var customers = (from c in _context.Customers where c.SaleOrders.Count > 0
let activeCount = c.SaleOrders.Count(so => so.Status != SaleOrderStatus.Cancelled)
orderby activeCount descending
select c).Take(10);
UPDATE
Thanks to Jon Skeet's comment about doing a double Sum, I wrote the following query which compiles.
var customers = (from c in _context.Customers where c.SaleOrders.Count > 0
let orderSum = c.SaleOrders.Where(so => so.Status != SaleOrderStatus.Cancelled)
.Sum(so => so.LineItems.Sum(li => li.CalculateTotal()))
orderby orderSum descending
select c).Take(10);
But when I run this, I get the following error:
It seems LINQ doesn't recognise my .CalculateTotal() method which sit on my LineItem.cs entity.
The problem you were seeing is that CalculateTotal() is not something that Linq can translate into SQL (which is done at run-time, hence no complier error).
The essential problem here is that Linq doesn't really work on lambdas (Func<>), but actually Expressions (Expression<Func<>>), which is the code in a partial compiled state, which Linq then goes about disassembling and translating into SQL.
So, let assume CalculateTotal is a member function defined like this:
public decimal CalculateTotal()
{
return this.quantity * this.value;
}
We could define that as a local lambda function
Func<LineItem, decimal> CalculateTotal = (li => li.quantity * li.value);
Now, we have a lambda which takes a LineItem and returns a value, which is exactly what Sum() wants, so now we can replace:
.Sum(so => so.LineItems.Sum(li => li.CalculateTotal()))
with
.Sum(so => so.LineItems.Sum(CalculateTotal))
But that will crash, just as it did before, because, as I said, it wants an Expression. So, we give it one:
Expression<Func<LineItem, decimal>> CalculateTotal = (li => li.quantity * li.value);

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 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.

How to return distinct rows from and Entity with related Entities

I have an Entity that has an association to other Entities (related entities). I'm trying to return distinct rows from the primary entity which needs to include the data from the related entity so I can use one the related entity's properties downstream.
Below is the statement I'm using but it is not returning any rows. What's the best way to do this?
Below is my code.
return context.UserDisplays.Include("CurrentJob").Where(d => d.UserName == userName).GroupBy(d => d.CurrentJob.JobNo).Select(g => g.FirstOrDefault()).ToList();
Any help would be greatly appreciated!
Edit - For ComplexProperty
I believe once you do a GroupBy all Include methods are ignored. So you will need to iterate the list and call the LoadProperty method on each item. It should look something like this
var list = context.UserDisplays
.Where(d => d.UserName == userName)
.GroupBy(d => d.CurrentJob.JobNo)
.Select(g => g.FirstOrDefault()).ToList();
foreach(var item in list)
{
context.LoadProperty(item, "CurrentJob");
}
return list;
Resource Link
Check out the Distinct (Set Operators) section in this article
http://msdn.microsoft.com/en-us/vcsharp/aa336746
Are you asking for the Distinct UserDisplays? or the Distinct User or the Disticnt Jobs?
I would try say something like
var object = (from userDisplay in context.UserDisplays.Include("CurrentJob")
.Where userDisplay.UserName == userName
Select userDisplay).Distinct();
(sorry, im going off of my VB style but it should be about the same...)

Resources