Property Sum translated to sql on IQueryable with linq to entities - c#

Given two classes generated code first with EF, with a parent-child relation:
class Parent {
//...
public virtual ICollection<Child> Children
}
class Child {
//...
public decimal Amount{ get; set; }
public decimal UnitPrice { get; set; }
}
I would like to create a property Total on Parent, something like
decimal Total => Children.Sum(child => child.UnitPrice * child.Amount)
But if I do this like that, and then do
var list = ctx.Parents
.Where(p => p.Total > 1000)
.Select(p => new {
p.Id,
p.Total,
Count = p.Children.Count });
foreach(var item in list){
Console.WriteLine($"Id: {item.} ...");
}
I got an error that says
An unhandled exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll
Additional information: The specified type member 'Total' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
While, at my opinion, it should not be that hard for EF to generate an query using SUM(UnitPrice * Amount) as Total I can't get it working.
I have already tried it with a static expression like
public static Expression<Func<Parent, decimal>> GetTotal()
{
return p=> p.Children.Sum(line => line.ItemQty * line.UnitPrice);
}
While it should be acceptable to do this calculation just in code. I want to use this example to learn more about how to use IQueryable.
A Little Succes
Given the following static property on Parent
public static Expression<Func<Parent, decimal?>> Total =>
p=> (from c in p.Childeren
select c.UnitPrice * c.ItemQty).Sum();
And then doing
var list = ctx.Parents.Select(Parent.Total);
I got a list containing all the totals, and I see that EF generated the following query
SELECT
(SELECT
SUM([Filter1].[A1]) AS [A1]
FROM ( SELECT
[Extent2].[UnitPrice] * CAST( [Extent2].[Amount] AS decimal(19,0)) AS [A1]
FROM [dbo].[Child] AS [Extent2]
WHERE [Extent1].[Id] = [Extent2].[ParentId]
) AS [Filter1]) AS [C1]
FROM [dbo].[Parent] AS [Extent1]
So, it EF is indeed capable of translating the Sum() method to SQL. now I only need to use it in the Where().

Calculated properties are only known in the class, not in the database. You'll need to instantiate your objects (into classes) before you can access these calculated properties.
It's not that hard to get this to work:
var list = ctx.Parents
.Where(p => p.Total > 1000)
.ToList() //This instantiates your data into objects
.Select(p => new {
p.Id,
p.Total,
Count = p.Children.Count });
This may require an include statement, depending on whether you've got lazy loading or not:
var list = ctx.Parents
.Include(p => p.Children) //Children will be populated upon instantiation
.Where(p => p.Total > 1000)
.ToList() //This instantiates your data into objects
.Select(p => new {
p.Id,
p.Total,
Count = p.Children.Count });
While, at my opinion, it should not be that hard for EF to generate an query using SUM(UnitPrice * Amount) as Total I can't get it working.
There's more to it than you're mentioning.
The SUM of what? Not of the current result (which is the Parents table), you want a sum of a calculation done on the Children table (grouped by ParentId), which is a completely different dataset than the one you've been working with so far.
This is why EF won't do what you want it to do. It's trying to decide on the SELECT part of the query, but your expectation requires a much more complex query body; one which joins children and parents together in order to perform a calculation on the children and then sum those outcomes into a single value.
If you try writing the SQL query yourself, you'll see that it's much more complicated than just doing SUM(UnitPrice * Amount) as Total.

Related

Equivalent of INNER JOIN ... WHERE in NHibernate

I have class Category that contains a List<Subcategory>.
Using LINQ (preferrably), I want to get all Categoriess but filter their Subcategories such that the resulting query would look like:
SELECT * FROM categories
LEFT JOIN subcategories ON subcategories.category_Id = categories.id WHERE subcategories.Retired = false
I have tried:
session.QueryOver<Category>()
.Left.JoinQueryOver(c => c.Subcategories, () => subcategoryAlis, s => !s.Retired)
.List<Category>())
And a few other hacky looking tricks but I can't seem to get the results I need. The above query produces completely useless results with the same entries appearing multiple times and many missing.
It's been a while since I've used NHibernate, but I would do it with some dedicated DTO classes for converting to Json, not directly from the entities. I think something along the lines of the following linq query should work:
from c in session.Query<Category>
select new CategoryDto {
Name = c.Name, //and other properties
SubCategories = c.SubCategories
.Where(sc => !sc.Retired)
.Select(sc => new SubCategoryDto { ... })
.ToList()
}

LINQ: Is there a way to combine these queries into one?

I have a database that contains 3 tables:
Phones
PhoneListings
PhoneConditions
PhoneListings has a FK from the Phones table(PhoneID), and a FK from the Phone Conditions table(conditionID)
I am working on a function that adds a Phone Listing to the user's cart, and returns all of the necessary information for the user. The phone make and model are contained in the PHONES table, and the details about the Condition are contained in the PhoneConditions table.
Currently I am using 3 queries to obtain all the neccesary information. Is there a way to combine all of this into one query?
public ActionResult phoneAdd(int listingID, int qty)
{
ShoppingBasket myBasket = new ShoppingBasket();
string BasketID = myBasket.GetBasketID(this.HttpContext);
var PhoneListingQuery = (from x in myDB.phoneListings
where x.phonelistingID == listingID
select x).Single();
var PhoneCondition = myDB.phoneConditions
.Where(x => x.conditionID == PhoneListingQuery.phonelistingID).Single();
var PhoneDataQuery = (from ph in myDB.Phones
where ph.PhoneID == PhoneListingQuery.phonePageID
select ph).SingleOrDefault();
}
You could project the result into an anonymous class, or a Tuple, or even a custom shaped entity in a single line, however the overall database performance might not be any better:
var phoneObjects = myDB.phoneListings
.Where(pl => pl.phonelistingID == listingID)
.Select(pl => new
{
PhoneListingQuery = pl,
PhoneCondition = myDB.phoneConditions
.Single(pc => pc.conditionID == pl.phonelistingID),
PhoneDataQuery = myDB.Phones
.SingleOrDefault(ph => ph.PhoneID == pl.phonePageID)
})
.Single();
// Access phoneObjects.PhoneListingQuery / PhoneCondition / PhoneDataQuery as needed
There are also slightly more compact overloads of the LINQ Single and SingleOrDefault extensions which take a predicate as a parameter, which will help reduce the code slightly.
Edit
As an alternative to multiple retrievals from the ORM DbContext, or doing explicit manual Joins, if you set up navigation relationships between entities in your model via the navigable join keys (usually the Foreign Keys in the underlying tables), you can specify the depth of fetch with an eager load, using Include:
var phoneListingWithAssociations = myDB.phoneListings
.Include(pl => pl.PhoneConditions)
.Include(pl => pl.Phones)
.Single(pl => pl.phonelistingID == listingID);
Which will return the entity graph in phoneListingWithAssociations
(Assuming foreign keys PhoneListing.phonePageID => Phones.phoneId and
PhoneCondition.conditionID => PhoneListing.phonelistingID)
You should be able to pull it all in one query with join, I think.
But as pointed out you might not achieve alot of speed from this, as you are just picking the first match and then moving on, not really doing any inner comparisons.
If you know there exist atleast one data point in each table then you might aswell pull all at the same time. if not then waiting with the "sub queries" is nice as done by StuartLC.
var Phone = (from a in myDB.phoneListings
join b in myDB.phoneConditions on a.phonelistingID equals b.conditionID
join c in ph in myDB.Phones on a.phonePageID equals c.PhoneID
where
a.phonelistingID == listingID
select new {
Listing = a,
Condition = b,
Data = c
}).FirstOrDefault();
FirstOrDefault because single throws error if there exists more than one element.

Order by in linq on joining

I have to show the movies which have most wanted to see status. For this I Join two tables 1. MovieInformation and 2. SeentItWantToSeeIt. I calculated most wanted to see movie with this code snippets.
public class RangkedMovieList
{
public int MovieID { get; set; }
public int count { get; set; }
}
rankedList = db.SeenItWantToSeeIt.Where(m => m.Status == 1 && m.MovieID != null)
.GroupBy(m => m.MovieID)
.Select(g => new RangkedMovieList
{ MovieID = g.Key.Value,
count = g.Count()
})
.ToList();
AllMovieInfo is Iqueryable with which I joined wanted to see movies.
AllMovieInfo = (from r in rankedList
join a in AllMovieInfo on r.MovieID equals a.MovieID
orderby r.count descending
select a).Distinct().AsQueryable();
This query works fine and orders the result by MovieID but I want to order all movies by their wanted to see count. Though I have written code to orderby r.count descending, Its not working. Please help me out.
I'm guessing your second LINQ query is operating at the database level. If so, it's likely that the Distinct call won't honor the previously specified sort order (in fact, if you drop the Distinct call and debug your output, it should appear sorted correctly). Typically for simple queries you can fix this by swapping the order of the sort (i.e., orderby) and Distinct call while keeping the query on the database level, as answered in another post, but your case is a little more complicated since you need to work with rankingList.
In your case you could bring it down to the LINQ to Objects level by adding a ToList() as you did for the first query, or an AsEnumerable(). At that level you wouldn't need to worry about swapping the order of the sort or distinct call. However, you'll likely need to use the overloaded Distinct method that accepts an IEqualityComparer and provide a comparer for AllMovieInfo. Check out the link for an example of how to implement that.
var query = (from r in rankedList
join a in AllMovieInfo on r.MovieID equals a.MovieID
select new { Info = a, Rank = r.count })
.AsEnumerable()
.OrderByDescending(o => o.Rank);
.Select(o => o.Info)
.Distinct(); // will likely need an IEqualityComparer here
// this could be combined with the above query, but I split it for clarity
AllMovieInfo = query.AsQueryable();
You might not need the AsQueryable at that point since it's already on the client at that point, in memory.

Join vs Navigation property for sub lists in Entity Framework

I have a sql statement like this:
DECLARE #destinations table(destinationId int)
INSERT INTO #destinations
VALUES (414),(416)
SELECT *
FROM GroupOrder grp (NOLOCK)
JOIN DestinationGroupItem destItem (NOLOCK)
ON destItem.GroupOrderId = grp.GroupOrderId
JOIN #destinations dests
ON destItem.DestinationId = dests.destinationId
WHERE OrderId = 5662
I am using entity framework and I am having a hard time getting this query into Linq. (The only reason I wrote the query above was to help me conceptualize what I was looking for.)
I have an IQueryable of GroupOrder entities and a List of integers that are my destinations.
After looking at this I realize that I can probably just do two joins (like my SQL query) and get to what I want.
But it seems a bit odd to do that because a GroupOrder object already has a list of DestinationGroupItem objects on it.
I am a bit confused how to use the Navigation property on the GroupOrder when I have an IQueryable listing of GroupOrders.
Also, if possible, I would like to do this in one trip to the database. (I think I could do a few foreach loops to get this done, but it would not be as efficient as a single IQueryable run to the database.)
NOTE: I prefer fluent linq syntax over the query linq syntax. But beggars can't be choosers so I will take whatever I can get.
If you already have the DestinationGroupItem as a Navigation-property, then you already have your SQL-JOIN equivalent - example. Load the related entities with Include. Use List's Contains extension method to see if the desired DestinationId(s) is(are) hit:
var destinations = new List<int> { 414, 416 };
var query = from order in GroupOrder.Include(o => o.DestinationGroupItem) // this is the join via the navigation property
where order.OrderId == 5662 && destinations.Contain(order.DestinationGroupItem.DestinationId)
select order;
// OR
var query = dataContext.GroupOrder
.Include(o => o.DestinationGroupItem)
.Where(order => order.OrderId == 5662 && destinations.Contain(order.DestinationGroupItem.DestinationId));

How can I query this hierarchical data using LINQ?

I have 3 kinds of objects: Agency, BusinessUnit and Client (each with their own respective table)
In terms of hierarchy, Agencies own BusinessUnits, and BusinessUnits own Clients.
I have 3 C# POCO Objects to represent them (I usually select new {} into them, rather than use the LINQ generated classes):
public class Agency
{
public IEnumerable<BusinessUnit> BusinessUnits { get; set; }
}
public class BusinessUnit
{
public IEnumerable<Client> Clients { get; set; }
}
public class Client
{
public int NumberOfAccounts { get; set; }
public Decimal AmountOfPlacement { get; set; }
public Decimal AvgBalance { get; set; }
public Double NeuPlacementScore { get; set; }
}
You can see that Agencies contain a list of BusinessUnits, and BusinessUnits contain a list of Clients.
I also have a mapping table called BAC_Map in the database which says which owns which, and it looks something like this:
How can I construct a query, so I can query for and return a list of Agencies? Meaning that, I want each Agency to have its list of BusinessUnit objects set, and I want the list of BusinessObjects to have its list of Clients set.
I can do basic LINQ queries, but this is a little over my head concerning the Map table and the multiple? queries.
How could I construct a method like GetAllAgencies() which would query, for not only all agencies, but populate its BusinessUnits that Agency owns, and the Clients those BusinessUnits own?
Edit: Any tips or info is appreciated. Do I need to do joins? Does this need to be multiple queries to return an Agency list, with its submembers populated?
If you drop all four tables (Agency, BusinessUnit, Client, Map) on the linq to sql designer, and draw relationships from Map to the other three, there will be some useful properties on Map.
//construct a query to fetch the row/column shaped results.
var query =
from m in db.map
//where m.... ?
let a = m.Agency
let b = m.BusinessUnit
let c = m.Client
// where something about a or b or c ?
select new {
AgencyID = a.AgencyID,
AgencyName = a.Name,
BusinessUnitID = b.BusinessUnitID,
ClientID = c.ClientID,
NumberOfAccounts = c.NumberOfAccounts,
Score = c.Score
};
//hit the database
var rawRecords = query.ToList();
//shape the results further into a hierarchy.
List<Agency> results = rawRecords
.GroupBy(x => x.AgencyID)
.Select(g => new Agency()
{
Name = g.First().AgencyName,
BusinessUnits = g
.GroupBy(y => y.BusinessUnitID)
.Select(g2 => new BusinessUnit()
{
Clients = g2
.Select(z => new Client()
{
NumberOfAccounts = z.NumberOfAccounts,
Score = z.Score
})
})
})
.ToList();
If approriate filters are supplied (see the commented out where clauses), then only the needed portions of the tables will be pulled into memory. This is standard SQL joining at work here.
I created your tables in a SQL Server database, and tried to recreate your scenario in LinqPad. I ended up with the following LINQ statements, which basically result in the same structure of your POCO classes:
var map = from bac in BAC_Maps
join a in Agencies on bac.Agency_ID equals a.Agency_ID
join b in BusinessUnits on bac.Business_Unit_ID equals b.Business_Unit_ID
join c in Clients on bac.Client_ID equals c.Client_ID
select new
{
AgencyID = a.Agency_ID,
BusinessUnitID = b.Business_Unit_ID,
Client = c
};
var results = from m in map.ToList()
group m by m.AgencyID into g
select new
{
BusinessUnits = from m2 in g
group m2 by m2.BusinessUnitID into g2
select new
{
Clients = from m3 in g2
select m3.Client
}
};
results.Dump();
Note that I called map.ToList() in the second query. This actually resulted in a single, efficient query. My initial attempt did not include .ToList(), and resulted in nine separate queries to produce the same results. The query generated by the .ToList() version is as follows:
SELECT [t1].[Agency_ID] AS [AgencyID], [t2].[Business_Unit_ID] AS [BusinessUnitID], [t3].[Client_ID], [t3].[NumberOfAccounts], [t3].[AmountOfPlacement], [t3].[AvgBalance], [t3].[NeuPlacementScore]
FROM [BAC_Map] AS [t0]
INNER JOIN [Agencies] AS [t1] ON [t0].[Agency_ID] = [t1].[Agency_ID]
INNER JOIN [BusinessUnits] AS [t2] ON [t0].[Business_Unit_ID] = [t2].[Business_Unit_ID]
INNER JOIN [Clients] AS [t3] ON [t0].[Client_ID] = [t3].[Client_ID]
Here is a screenshot of the results:
alt text http://img411.imageshack.us/img411/5003/agencybusinessunitclien.png
If you are doing this with direct LINQ to SQL, there is no way to do this without some kind of recursion, whether you do it yourself or you hide it behind an extension method. Recursive SQL is very bad (many round trips, many single queries).
There are two options here. One is to pull the entire table(s) with the hierarchy into memory and use LINQ to Objects on it. Leave the "details" tables in SQL. If you have less than several thousand entities, this is probably the most efficient way to go. You can keep a single copy of the table(s) in cache and refresh them when necessary. When you need to fetch more detailed data from the DB for a single record, you can reattach that entity from your cached hierarchy to a new DataContext and fetch it.
The other option is to use a more complex relationship model in your database. Storing parent only by nature demands recursion, but you can use the adjacency list model to construct a single query which can span many levels of inheritance. This will mean your LINQ to SQL queries become less intuitive (querying against Entity.Right and Entity.Left isn't quite as pretty as Parent or Children...) but you can do in one query what might take hundreds or thousands in the literal recursive approach.

Categories

Resources