C# Entity Framework Pagination Children Includes - c#

I am looking for the fastest way to do the following operation. What I need to accomplish, is I have a screen that displays the "Parts" that are defined inside of a "Lot". Each part has objects of a station, and each station has objects of tools, and each tool can have measurements.
My problem is I cannot get the pagination to work. The incoming offset is 0, and the number of records to take is 20 however the following operation is not working:
Lot foundLot = EntitiesContext.Lots.Where(x => x.ID == lotID)
.IncludeFilter(lot => lot.Parts.OrderBy(n => n.PartID).Skip(offset).Take(numberOfRecords).ToList())
.IncludeOptimized(lot => lot.Parts.Select(parts => parts.Stations))
.IncludeOptimized(lot => lot.Parts.Select(parts => parts.Stations.Select(station => station.Tools)))
.IncludeOptimized(lot => lot.Parts.Select(parts => parts.Stations.Select(station =>
station.Tools.Select(tools => tools.Measurements))))
.FirstOrDefault();
So I am trying to filter to only grab certain parts, and then of those filtered parts, I want to grab all of the children's data associated with them. I have checked all of the existing stack overflow articles related to this, and the changes I make either result in the Z.EntityFrameworkPlus package throwing a generic exception, that provides no details (which is what the above code does), or if I use the regular EntityFramework functions it throws an exception for the invalid path.
Thank you for your assistance.

Okay, after reading the docs for Z.EntityFrameworkPlus I now see you cannot use IncludeFilter and IncludeOptimzed on the same LINQ statement
https://entityframework-plus.net/query-include-optimized
I switched IncludeFilter to IncludeOptimzed and it works.

Related

How to query based on an previous object result? Entity Framework

I am extremely stuck with getting the right information from the DB. So, basically the problem is that I need to add where closure in my statement to validate that it only retrieves the real and needed information.
public async Task<IEnumerable<Post>> GetAllPosts(int userId, int pageNumber)
{
var followersIds = _dataContext.Followees.Where(f => f.CaUserId == userId).AsQueryable();
pageNumber *= 15;
var posts = await _dataContext.Posts
.Include(p => p.CaUser)
.Include(p => p.CaUser.Photos)
.Include(c => c.Comments)
.Where(u => u.CaUserId == followersIds.Id) <=== ERROR
.Include(l => l.LikeDet).ToListAsync();
return posts.OrderByDescending(p => p.Created).Take(pageNumber);
}
As you can see the followersIds contains all the required Id which I need to validate in the post variable. However I have tried with a foreach but nothing seems to work here. Can somebody help me with this issue?
The short version is that you can change that error line you have marked above to something like .Where(u => followersIds.Contains(u.CaUserId) which will return all entities with an CaUserID that is contained in the followersIds variable, however this still has the potential to return a much larger dataset than you will actually need and be quite a large query. (You also might need to check the sytax just a bit, shooting from memory without an IDE open) You are including a lot of linked entities in that query above, so maybe you'd be better off using a Select query vs a Where query, which would load only the properties that you need from each entity.
Take a look at this article from Jon Smith, who wrote the book "Entity Framework Core In Action", where he talks about using Select queries and DTO's to only get out what you need. Chances are, you don't need every property of every entity you are asking for in the query you have above. (Maybe you do, what do I know :p) Using this might help you get something much more efficient for just the dataset you need. More lines of code in the query, but potentaily better performance on the back end and a lighter memory footprint.

Formatting What's returned from LINQ

So, I currently have a LINQ query
BStops.JPPlatforms.Platform
.Where(Stop => Stop.Name.ToLower().Contains(SearchBox.Text.ToLower()))
.Select(Stop => new { Stop.Name, Stop.PlatformNo })
.ToList();
Which is returning the data I expect it to, the property Platform contains a list of stops that hold another class with properties I want to access to such as Name, PlatformNo and PlatforTag, now the killer for me is two things, one is less important at the moment but if you can help it would be great!
So I want to format this output so when you search it doesn't have all this garbled stuff around it, I would prefer it to be like
Annex Rd near Railway (50643)
I've tried adjusting my query to be like
BStops.JPPlatforms.Platform
.Where(Stop => Stop.Name.ToLower().ToString().Contains(SearchBox.Text.ToLower().ToString()))
.Select(Stop => String.Format("{0} ({1})",new { Stop.Name, Stop.PlatformNo }))
.ToList();
But that causes it to crash back to a unhanded exception, for the life of me I can't seem to figure this out, as for the second part. I'd also like my LINQ query to search both the Name and PlatformNo properties I've already tried the logical || but it crashes back to an unhanded exception and I don't know enough about LINQ to figure out why, any help at this point would be great :).
Changing your LINQ query to this would solve the problem.
BStops.JPPlatforms.Platform.Where(Stop => Stop.Name.ToLower()
.Contains(SearchBox.Text.ToLower()))
.Select(Stop => new
{
StopAddress = $"{Stop.Name} {Stop.PlatformNo}"
})
.ToList();
The Where clause is not performant. The Text.ToLower should be done outside of the Linq. Also ToLower returns a string, so there is no need go call ToString
The Select should not create a new object.
var text = SearchBox.Text.ToLower();
BStops.JPPlatforms.Platform
.Where(stop => stop.Name.ToLower().Contains(text))
.Select(stop => String.Format("{0} ({1})", stop.Name, stop.PlatformNo))
.ToList();

Querying across relationships with LINQ to Entity Framework

Here is what my model looks like:
I'm trying to get the count of distinct Assesors by a certain EventId.
Here's the code I'm trying to use:
var x = db.Assessors.Select(a => (a.Assessments.Select(y => y.EventFacility.EventId == 138))).Count();
Unfortunately, I must be coding this wrong because instead of getting the expected result (a count of 9, in this case) I'm getting the wrong result: 35.
I'm wondering if someone can take a look at my LINQ statement and tell me what I'm doing wrong?
You need to use Where and Any like this:
var result = db.Assessors
.Where(a => a.Assessments.Any(y => y.EventFacility.EventId == 138));
What this is saying is that you want all Assessors that are parents of any Assessment that is related to that particular Event.
You're going about this backward, start from what you know (the event since you have it's ID) and navigate to what you want through the navigation properties.
It's impossible to tell from your schema as it includes neither the properties nor the mapping type 1:1? 1:N? Can't know from simple lines
It would probably look Something like this
var x = db.Events
.Where(ev=>ev.Id == 138)
.SelectMany(ev=>ev.EventFacilities) //(i'm assumine possibly multiple and not 1 per event, once again schema doesn't show it, if it's not the case change SelectMany to Select)
.SelectMany(ef=>ef.Assesments) // Same assumption as above
.Select(as=>as.Assessor) // Asuming otherwise here, if wrong change Select to SelectMany
.Distinct(); // Ignore duplicate assessors
Note that your question is impossible to answer as is, this is a best effort but if you want help you should give "all" the information required, not strip out what doesn't immediately seem relevant, it would've been much easier if you took an actual screenshot of your entity diagram instead of what you made up.

EntityFramework - How to populate child members?

I have been given a model to work with but cannot figure out the LINQ query to get what I need into my object.
Given an initial date, I need to retrieve all of the items posted for sale since then, member details, and the filenames of the photos of those items.
Also need to be able to pass in a MemberID and find which items they have for sale (and filenames)
Can someone please point me to what the query for this would be?
I've been following a few EF tutorials but am having lots of trouble figuring this one out...
When I try this, I get a null reference exception when talking to the photoURL.
var items = db.BuySellLists.Include(b => b.Member)
.Include(b => b.BuySellPhoto)
.Where(b => b.PostedDate >= fromDate && b.IsBuyable == true)
.OrderBy(b => b.PostedDate)
.ToList();
I've currently got it working by looking up all items for sale, then from there looping over each BuySellListId to get that items photos to get the URL, but this causes quite a few individual queries to the database and is giving performance issues.
Photo is not implicitly included in your query. That is why you get a NullReferenceException. You need to include it explicitly as follows:
Include(b => b.BuySellPhoto.Photo)
Check MSDN documentation on Include extension for more details.

Linq Take on Include?

I have a query with a lot of includes, and I'm wondering if I can do Takes on some of the includes.
For example, here's one of my queries, with the (illegal) Take illustrating what I want to do.
var primaryLocation = context.Locations
.Include("PhoneNumbers")
.Include("Invoices").Take(50)
.Include("Invoices.Items")
.Include("Schedules")
.Include("Staffs")
.SingleOrDefault(d => d.Id == locationId);
Currently the only way I can think to do it would be like so:
var primaryLocation = context.Locations
.Include("Invoices")
.Include("Etc")
.SingleOrDefault(d => d.Id == locationId);
primaryLocation.Invoices = primaryLocation.Invoices.Take(50).ToList();
I'd prefer not doing it that way, since means pulling back the entire Invoice list from the database, which I don't need.
Is there a handy way to build the Take into my query?
Seems like have two conflicting criteria for what you're doing. I'm guessing here, but you didn't leave us all that much to go on.
Since your primaryLocation.Invoices = primaryLocation.Invoices.Take(50).ToList(); statement only makes use of 1 of your includes, I'm assuming you're doing more things with your primaryLocation than what you've shown us. This leads me to believe that you want that primaryLocation to include all of the stuff. And then you seem not to want more than those 50, so that's not all of the stuff after all then... To me this is a contradiction. If you require all, you should include it all.
If you want your 50 invoices selection specifically you could get those separately in its own query. I use NHibernate myself, so I'm not sure of the syntax for future's in Entity framework, but if you want to ask for multiple things with only 1 round-trip to the server, in NHibernate you can make a series of queries into futures to allow this. I expect Entity framework has something similar.
In short, what I'm suggesting is that if you want primaryLocation to include all of your data, then that's what you'll get, and if you're after more specific information with filters like Take, then you might want to query more specifically.
Use projection instead of blindly calling Include if you don't want everything:
var primaryLocation = context.Locations
.Select(location => new {
Id = location.Id,
Name = location.Name,
// ... other properties needed on the front end
RecentInvoices = location.Invoices
// really should sort if you're only taking 50
.OrderByDescending(invoice => invoice.CreatedAt)
.Take(50),
AllPhoneNumbers = location.PhoneNumbers,
})
.SingleOrDefault(location => location.Id == locationId);
You could use projection to get just the invoice information you need too, I just didn't want to over-complicate the example.
Using this method you get exactly the data you want without adding confusion. It also allows you to name your properties (such as RecentInvoices above) to add more meaning.

Categories