Linq extension methods - how to include not loaded objects? - linq

I have something like this:
var threads = _forumsDb.ForumsAccounts
.Where(a => a.UserName == User.Identity.Name)
.SelectMany(u => u.Threads);
But those thread entities have posts entities associated with them, which are not loaded due to lazy loading. If i do something like this:
var threads = _forumsDb.ForumsAccounts
.Include("Posts")
.Where(a => a.UserName == User.Identity.Name)
.SelectMany(u => u.Threads);
It's apparently not working, and those thread entities still have null Posts entities. How can i do it correctly?

You can't apply Include for properties in a projection (Select or SelectMany). You need to extend the projection to include the posts as an additional property:
var threads = _forumsDb.ForumsAccounts
.Where(a => a.UserName == User.Identity.Name)
.SelectMany(u => new
{
Threads = u.Threads,
ThreadPosts = u.Threads.Select(t => t.Posts)
})
.AsEnumerable()
.SelectMany(a => a.Threads)
.ToList();
If the relationship between Thread and Post is one-to-many EF will fill the Posts collection of the threads.
The query would be much easier though if you have an inverse ForumAccount property on the Thread entity:
var threads = _forumsDb.Threads
.Include("Posts")
.Where(t => t.ForumAccount.UserName == User.Identity.Name)
.ToList();
In this case eager loading with Include will work.

Related

Linq most efficient top results

I'm wondered I have a table with IDs and a version and a remove field. I d like to return the the top 20 records grouped by ID and for ech ID take only the highest version unless remove is set then ignore removed records.
Then return a descending record set.
There are a few ways todo it with Linq but I wonder is there a most efficient way, are there patterns to avoid?.
...
.OrderByDescending(x=>x.id)
.GroupBy(x=>x.id)
.SelectMany(y=>y.Where(x=>x.Version == y.Max(y=>y.Version)))
.Where(x=>x.Remove=false)
.Take(20)
One of then possible workarounds when using EF Core. I'm calling it workaround because with SQL and Window functions we can create more effective query.
var itemsQuery = ctx.SomeTable
.Where(x => x.Remove = false);
var query =
from d in itemsQuery.Select(d => new { d.id }).Distinct()
from x in itemsQuery.Where(x => d.Id == x.Id)
.OrderByDescending(x => x.Version)
.Take(1)
select x;
query = query.Take(20);
Similar queries when using EF Core 6:
var query = ctx.SomeTable
.Where(x => x.Remove = false)
.GroupBy(x => x.Id)
.Take(20)
.SelectMany(g => g.OrderByDescending(x => x.Version).Take(1));
var query = ctx.SomeTable
.Where(x => x.Remove = false)
.GroupBy(x => x.Id)
.Select(g => g.OrderByDescending(x => x.Version).First());
.Take(20);

Linq eager load chain entities

Why candidate.Qualification is always null in all followings attempts:
var candidate = await _acceptedOfferRepository.GetAll()
.Include(c => c.Candidate)
.Where(ao => ao.Candidate.Id == candidateId && ao.EmployerId.Id == employerId)
.Select(c => c.Candidate)
.Include(c => c.Qualification)
.FirstOrDefaultAsync();
Or:
var candidate = await _acceptedOfferRepository.GetAll()
.Include(c => c.Candidate.Qualification)
.Where(ao => ao.Candidate.Id == candidateId && ao.EmployerId.Id == employerId)
.Select(c => c.Candidate)
.FirstOrDefaultAsync();
Or:
var candidate = await _acceptedOfferRepository.GetAll()
.Include(c => c.Candidate)
.ThenInclude(c => c.Candidate.Qualification)
.Where(ao => ao.Candidate.Id == candidateId && ao.EmployerId.Id == employerId)
.Select(c => c.Candidate)
.FirstOrDefaultAsync();
There is a 1:1 relationship between Candidate and his\her
Qualification
The _acceptedOfferRepository.GetAll() returns
IQueryable
I think something is wrong with the Select() before the FirstOrDefaultAsync(). As soon as I remove that it works fine and all the chained entities are loaded properly. But then it returns the first AcceptedOffer whereas I need the Candidate!
Queries from failed attempts fall into Ignored includes category:
If you change the query so that it no longer returns instances of the entity type that the query began with, then the include operators are ignored.
They are pretty much like the example from the link. Queries start with AcceptedOffer, but then Select changes the result type to Candidate, hence all Include / ThenInclude are simply ignored.
First thing I would suggest is to configure EF Core to throw exception for ignored includes as explained in the link:
optionsBuilder.ConfigureWarnings(warnings => warnings
.Throw(CoreEventId.IncludeIgnoredWarning));
Second, try building your entity returning queries starting from the entity you want to return. For instance, with your sample it could be something like this (in case you have inverse navigation property from Candidate to AcceptedOffer and corresponding repository):
var candidate = await _candidateRepository.GetAll()
.Include(c => c.Qualification)
.Include(c => c.AcceptedOffers) // optional
.Where(c => c.Id == candidateId &&
c.AcceptedOffers.Any(ao => ao.EmployerId.Id == employerId))
.FirstOrDefaultAsync();
This works fine:
var candidate = (await _acceptedOfferRepository.GetAll()
.Include(c => c.Candidate)
.ThenInclude(c => c.Candidate.Qualification)
.Where(ao => ao.Candidate.Id == candidateId && ao.EmployerId.Id == employerId)
.FirstOrDefaultAsync())?.Candidate;
But still not quite sure why the Select() was not loading the Qualification!

Is it possible to filter the results of an included entity in the following LINQ statement

I would like to filter the Users entity in this LINQ statement by a boolean property it contains. Is that possible?
var Subscriber = db.Subscribers
.Include(s => s.Users)
.Include(s => s.SubscriberNotes)
.Include(s => s.Orders)
.Include(s => s.Websites.Select(w => w.DomainNames))
.Single(s => s.SubscriberId == id);
Something like Where S.Users.isOwner = true
Since I do not have the original dataset available, I'll give you some code from the top of my head - I guess you could do something like this:
db.Subscribers.Include(s => s.Users.Where(user => user.IsOwner == true))
... and then continue with the remaining Include statements.

linq to entities group by sub-query

I have the following code:
var statements = db.statement
.OrderByDescending(d => d.id)
.Take(5)
.AsEnumerable()
.Select(d => new Statements
{
Accounts = d.statement_entry.AsEnumerable()
.GroupBy(b => b.currency)
.Select(b =>
new Account
{
In = b.Where(l => l.amount > 0).Sum(l => l.amount),
Out = b.Where(l => l.amount < 0).Sum(l => l.amount),
Balance = b.Sum(l => l.amount),
Currency = b.Key
}).OrderBy(b => b.Currency),
UploadedDate = d.uploaded_date,
Id = d.id
})
.ToList();
Is there a way that I could do it without the AsEnumerable()? From what I understand the AsEnumberable will cause a query to take place for each of the statements returned.
Or is there a better way to refactor the code?
You understand wrongly. AsEnumerable will make the query execute on the local (client) machine.
This
statements = db.statement
.OrderByDescending(d => d.id)
.Take(5)
will be executed on the (SQL) server,
the remaining part on the client
Why are you puttin the AsEnumerable? I think the query should work even without (and it would do everything server-side)
The only thing is that after the OrderBy(b => b.Currency) you should put a .ToList() so that the .Select(b => new Account is materialized and cached.

EF Code first Eager loading and OrderBy problem

I have two entities called Category and Product with 1:n relation.
I want to get a Category with its childs that childs be in order.
This is my linq:
_db.Categories.Where(c => c.CategoryID == catID)
.Include(c => c.Products.OrderBy(p => p.ProductID))
.SingleOrDefault();
This query enforce with the below exception because of orderby.
The Include path expression must refer
to a navigation property defined on
the type. Use dotted paths for
reference navigation properties and
the Select operator for collection
navigation properties. Parameter name:
path
Eager loaded data cannot be ordered or filtered. That is linq-to-entities limitation and the only way how to order relations in the database is by using projection:
var data = _db.Polls
.Where(c => c.CategoryID == pollID)
.Select(c => new
{
Pool = c,
Products = c.Products.OrderBy(p => p.ProductID)
})
.SingelOrDefault();
You can project to anonymous or custom type but you cannot project to mapped type (for example Poll).
Another way is dividing this to two queries and use explicit loading:
var poll = _db.Polls.SingleOrDefault(c => c.CategoryID == pollID);
_db.Entry(poll).Collection(c => c.Products)
.Query()
.OrderBy(p =>.ProductID)
.Load();
Include has to reference a navigation property, which means you can't include an OrderBy(). Instead of this:
_db.Categories
.Where(c => c.CategoryID == catID)
.Include(c => c.Products.OrderBy(p => p.ProductID))
.SingleOrDefault();
...you'll have to use this:
_db.Categories
.Where(c => c.CategoryID == catID)
.Include(c => c.Products)
.SingleOrDefault();
...to access an ordered list of Products for each Category, you could add a property to Category like this:
class Category
{
public IEnumerable<Product> OrderedProducts
{
get { return this.Products.OrderBy(p => p.ProductID); }
}
}

Resources