Cant add Includes (eager-loading) to correct place - linq

I'm trying to speed up some of my db tier functions by adding the proper Include statements to force eager loading and reduce the number of queries I make to the database.
However in 90% of the cases I run into the problem that I'm not at the correct "starting location" to enter the includes I want.
The simplest example I could come up with:
I have Department, Course and Student entities with many-to-many relationships between them (some courses below to multiple departments).
Now I have a function
GetMasterCourses(Department dep) which does something like
return dep.Courses.Where(c => c.level == "Master")
The question is: how do I tell EF to load all students associated with each queried course?
The only solutions I have found are things like:
courseIDs = dep.Courses
.Where(c => c.level == "Master").Select(c => c.courseID)
dbcontext.Courses
.Include("Students")
.Where(c => courseIDs.Contains(c.courseID) and c.level == Master)
This seems rather silly to have to do such a workaround just to be able to specify the correct Include. I have looked at many examples of Include and searched many questions on stackoverflow but can't really find anyone with this problem, eventhough it seems a quite common problem.

I don't see why you are selecting IDs. It looks like you could just:
var courses = dep.Courses
.Include(i => i.Students)
.Where(c => c.level == "Master")
.ToList() // or whatever

If you are using DbContext (EF >= 4.1):
dbContext.Departments.Attach(dep);
dep.Courses = dbContext.Entry(dep).Collection(d => d.Courses).Query()
.Include(c => c.Students)
.Where(c => c.level == "Master")
.ToList();
If you are using ObjectContext with EntityObject derived entities (not POCOs):
objectContext.Departments.Attach(dep);
dep.Courses.Attach(dep.Courses.CreateSourceQuery()
.Include("Students")
.Where(c => c.level == "Master")
.ToList());
If you are using ObjectContext with POCOs it's possible but a bit hairy.
All queries above are actually not the same like yours because maybe your dep.Courses collection does not contain all the courses that are related to the department dep in the database. (Who knows if you didn't remove a course from the dep.Courses collection after loading it?) In this case I'm afraid your query is the only way to go.
Because you were talking about performance and database request optimization: If you want to avoid the overhead to load the courses again (which you already have in memory) you could also try:
courseIDs = dep.Courses
.Where(c => c.level == "Master").Select(c => c.courseID);
var studentDict = dbcontext.Courses
.Where(c => courseIDs.Contains(c.courseID))
.Select(c => new
{
courseID = c.courseID,
Students = c.Students
})
.ToDictionary(x => x.courseID, x => x.Students);
foreach (var course in dep.Courses)
course.Students = studentDict[course.courseID];
It loads less data but is not necessarily more performant because Contains has significant costs of translation into SQL for large courseIDs collections.

Related

How to improve performance of LINQ query to get many fields from a table

Suppose, I have a table called Accounts which has information like
bool isManaged,
double CurrentTotalBalance,
double CurrentManageableBalance,
AccountDetails AccountDetail table,
around 20 fields and 5 different tables
I want to write a query that can select only the field that I need not all of the tables as it reduces my performance.
Currently, I have something like this,
This is just a sample code, but not the working code.
var accounts = await _context.Accounts
.Include(x => x.Plan)
.Include(x => x.AccountDetails)
.Where(x => x.Plan.Id == 123)
.ToListAsync();
Then I am going, over the accounts list and getting the required results.
It works great for smaller plans but when I have larger plans it loads so many accounts.
So, is there a way to get data from it,
I was thinking something like this,
This is just a sample code, but not the working code.
var accounts = await _context.Accounts
.Include(x => x.Plan)
.Include(x => x.AccountDetails)
.Where(x => x.Plan.Id == 123)
.Select(x => new
{
PlanAssets = x.Sum(x => x.CurrentTotalBalance),
PlanParticipants = x.Accounts.Count,
OutsideAssets = 123,
ManagedParticipants = x.Where(x => x.IsManaged == 1).Count()
})
.ToListAsync();
But the problem with this is I am getting a list of each account. I want to sum up all the CurrentTotalBalance in the accounts table.
Is there a way to get the list of those fields which are required only from the accounts table and query them? I am having a performance issue. Any recommendation to improve the performance would be appreciated.

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

Nhibernate Fetch/FetchMany/ThenFetch duplicate results

I'm quering my database. The structure looks like below
Country 1..M CountryLocales
1
..
M
Cities 1..M CityLocales
So, each Country has multiple locales, each City has multiple locales and a Country has multiple cities.
I try to retrieve a city from the database. I want to prefetch the Citylocales, the country and the country locales.
To do this I perform this query:
City city = Session.Query<City>()
.Where(x => x.Id == id)
.Fetch(c => c.Country)
.ThenFetch(c => c.CountryLocales)
.FetchMany(x => x.CityLocales)
.AsEnumerable()
.FirstOrDefault();
For some reason I now get both duplicate records for the CountryLocales and for the CityLocales (both twice)
How can I fix this?
You should look into the Future method. This allows you to perform many feteches without bumping into this issue. Your current query is returning a Cartesian Product which you don't want. Using the Future method you can perform multiple queries each using one Fetch whose results are then aggregated together thus resulting in the desired result.
It might go something like this:
var result = Session.Query<City>()
.Where(x => x.Id == id)
.Fetch(c => c.Country)
.ToFuture();
Session.Query<City>()
.Where(x => x.Id == id)
.Fetch(c => c.CountryLocales)
.ToFuture();
Session.Query<City>()
.Where(x => x.Id == id)
.Fetch(c => c.CityLocales)
.ToFuture();
// execute query
City city = result.AsEnumerable().FirstOrDefault();
Take a look at this answer for more information: Multiple Fetches in linq to nhibernate

How do I merge two LINQ statements into one to perform a list2.Except(list1)?

Currently, I have the following LINQ queries. How can I merge the two queries into one. Basically, write a LINQ query to bring back the results I'd get from
IEnumerable<int> deltaList = people2010.Except(allPeople);
except in a single query.
var people2010 = Contacts.Where(x => x.Contractors
.Any(d => d.ContractorsStatusTrackings
.Any(date => date.StatusDate.Year >= 2010)))
.Select(x => x.ContactID);
var allPeople = Contacts.Where(x => x.Contractors
.Any(m => m.ContactID == x.ContactID))
.Select(x=> x.ContactID);
Thanks!
Why can you not just do Except as you are doing? Don't forget that your people2010 and allPeople variables are just queries - they're not the data. Why not just use them as they are?
If that's not acceptable for some reason, please give us more information - such as whether this is in LINQ to Object, LINQ to SQL etc, and what's wrong with just using Except.
It sounds like you're just looking for a more elegant way to write your query. I believe that this is a more elegant way to write your combined queries:
var deltaList =
from contact in Contacts
let contractors = contact.Contractors
where contractors.Any(ctor => ctor.ContractorStatusTrackings
.Any(date => date.StatusDate.Year >= 2010))
&& !contractors.Any(m => m.ContactID == contact.ContactID)
select contact.ContactID

Resources