Filter in Entity Framework Include - c#

I need to include records of the child table with filtering on a condition Status='Completed'. I tried all the possible ways like Any(), IncludeFilter(), but I can't achieve what I'm looking for. I went through all the posts related to this query but no solution.
return await db.Jobs
.Where(x => x.Account == id &&
x.Status == "Completed")
.Include(x => x.Account1)
.Include(x => x.Bids.Select(s => s.Account1))
.ToListAsync();
I can filter on the main table Jobs, but I also need to filter the child table Bids. I short - I need jobs that are completed with bids whose status is Completed.

Include does not filter, it merely includes related data to be loaded eagerly along with the main entity being selected.
To get jobs where the account status is completed and has at least one bid with a status of completed:
db.Jobs.Where(x => x.Account == id
&& x.Status == "Completed"
&& x.Bids.Any(b => b.Status == "Completed"))
.Include(x => x.Account1)
.ThenInclude(b => b.Account1)
.ToListAsync();
The typical thing that trips people up when they return entities is that they'd say "but I only want back the Bids that are completed." This will return back all bids for jobs that are completed and have at least 1 completed bid.
To return back a filtered set of related data to those jobs, use ViewModels/DTOs to return to the views/API consumers rather than returning entities. Entities jobs are to reflect the data state. A job may include many bids, so the entity should reflect the complete data state for that job.
To return completed jobs with their completed bids, define POCO view model classes for the job, bid, etc. then use .Select() to project the entities into the view models:
var viewModels = db.Jobs
.Where(x => x.Account == id
&& x.Status == "Completed"
&& x.Bids.Any(b => b.Status == "Completed"))
.Select(x => new JobViewModel
{
AccountId = x.Account,
Account = x.Account1.Select(a => new AccountViewModel
{
AccountId = a.Id,
// ...
},
Bids = x.Bids.Where(b => b.Status == "Completed)
.Select(b => new BidViewModel
{
BidId = b.Id,
// ...
}).ToList()
}).ToListAsync();

Related

Linq - filter a child list<object> without affecting Parent row count

I have a scenario where I need to filter on a child list<object> ResponseIssues that is being included with the parent Question which is also a list<object>. For this example, I have 10 questions I'm pulling back from a table that I will always need to pull back whether or not there are ResponseIssues.
There appears to be a couple of problems with my query. The first problem is that the number of Questions goes from 10 to 1 since I currently only have one question associated with ResponseIssues. I need all questions to come back.
The second problem is that when I look closer at the ResponseIssues child list<object>. While I'm seeing records that are associated with the question, it's not filtering out rows by SuveryPeriod and RespondentByQuarterId. I'm expecting one row and I'm getting three rows where two of the rows where from a previous period. The same issue happens for the Responses child list.
Here's my current code below. Any ideas on how to restructure the query where it factors in the above issues and returns a Questions object and not something anonymous?
var question = await _dbContext.Questions
.Include(x => x.Responses)
.Include(x => x.ResponseIssues)
.Include(x => x.SurveySection)
.Include(x => x.Survey)
.Where(x => x.SurveyId == surveyId &&
x.Responses.Any(r => r.SiteUserId == siteUserId &&
r.SurveyPeriodId == surveyPeriodId &&
r.RespondentByQuarterId == 2
) &&
x.ResponseIssues.Any(ri => ri.SurveyPeriodId == surveyPeriodId &&
ri.RespondentByQuarterId == 2
))
.OrderBy(x => x.Position)
.ToListAsync();
I was able to do the above by breaking it out into three separate queries rather than just one. I still would be curious to know if someone in the community has a way to do it as one query though.
Anyway, here's my code below. I'm able to update the Questions Parent with the correct number of rows for both Responses and ResponseIssues along with returning all of the questions.
var question = await _dbContext.Questions
.Include(x => x.SurveySection)
.Include(x => x.Survey)
.Where(x => x.SurveyId == surveyId)
.OrderBy(x => x.Position)
.ToListAsync();
var responses = await _dbContext.Responses
.Where(x => x.SiteUserId == siteUserId &&
x.SurveyPeriodId == surveyPeriodId)
.ToListAsync();
var responseIssues = await _dbContext.ResponseIssues
.Where(x => x.SurveyPeriodId == surveyPeriodId &&
x.SiteUserId == siteUserId)
.ToListAsync();
foreach (var item in question)
{
var foundResponse = responses.Where(x => x.QuestionId == item.Id).ToList();
var foundResponseIssue = responseIssues.Where(x => x.QuestionId == item.Id).ToList();
if (foundResponse != null)
{
item.Responses = foundResponse;
}
if (foundResponseIssue != null)
{
item.ResponseIssues = foundResponseIssue;
}
}

How can I order in Linq Select Distinct if ordered by an Included entity?

I know that in Linq I have to do the OrderBy after doing a Select - Distinct, but I'm trying to order by an Included entity property that get lost after the Select.
For example:
var accounts = _context.AccountUser
.Include(o => o.Account)
.Where(o => o.UserId == userId || o.Account.OwnerId == userId)
.OrderByDescending(o => o.LastAccessed)
.Select(o => o.Account)
.Distinct();
As I'm doing the Where by an or of two different parameters, there is a good chance to obtain duplicated results. That's why I'm using the Distinct.
The problem here is that after I do the Select, I don't have the LastAccessed property anymore because it doesn't belong to the selected entity.
I thing the structure of the AccountUser and Account can be inferred from the query itself.
If you have the bi-directional navigation properties set up:
var accountsQuery = _context.AccountUser
.Where(o => o.UserId == userId || o.Account.OwnerId == userId)
.Select(o => o.Account)
.Distinct()
.OrderByDescending(a => a.AccountUser.LastAccessed);
When Selecting the Account you do not need .Include() Keep in mind that any related entities that you access off the Account will be lazy-loaded. I recommend using a .Select() to extract either a flattened view model or a view model hierarchy so that the SQL loads all needed fields rather than either eager-loading everything or tripping lazy-load calls.
Since LINQ doesn't implement DistinctBy and LINQ to SQL doesn't implement Distinct that takes an IEqualityComparer, you must substiture GroupBy+Select instead:
var accounts = _context.AccountUser
.Include(o => o.Account)
.Where(o => o.UserId == userId || o.Account.OwnerId == userId)
.GroupBy(o => o.Account).Select(og => og.First())
.OrderByDescending(o => o.LastAccessed)
.Select(o => o.Account);

Filtering on Include in EF Core

I'm trying to filter on the initial query. I have nested include leafs off a model. I'm trying to filter based on a property on one of the includes. For example:
using (var context = new BloggingContext())
{
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList();
}
How can I also say .Where(w => w.post.Author == "me")?
Entity Framework core 5 is the first EF version to support filtered Include.
How it works
Supported operations:
Where
OrderBy(Descending)/ThenBy(Descending)
Skip
Take
Some usage examples (from the original feature request and the github commmit)
:
Only one filter allowed per navigation, so for cases where the same navigation needs to be included multiple times (e.g. multiple ThenInclude on the same navigation) apply the filter only once, or apply exactly the same filter for that navigation.
context.Customers
.Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.OrderDetails)
.Include(c => c.Orders).ThenInclude(o => o.Customer)
or
context.Customers
.Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.OrderDetails)
.Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.Customer)
Another important note:
Collections included using new filter operations are considered to be loaded.
That means that if lazy loading is enabled, addressing one customer's Orders collection from the last example won't trigger a reload of the entire Orders collection.
Also, two subsequent filtered Includes in the same context will accumulate the results. For example...
context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))
...followed by...
context.Customers.Include(c => c.Orders.Where(o => o.IsDeleted))
...will result in customers with Orders collections containing all orders.
Filtered Include and relationship fixup
If other Orders are loaded into the same context, more of them may get added to a customers.Orders collection because of relationship fixup. This is inevitable because of how EF's change tracker works.
context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))
...followed by...
context.Orders.Where(o => o.IsDeleted).Load();
...will again result in customers with Orders collections containing all orders.
The filter expression
The filter expression should contain predicates that can be used as a stand-alone predicate for the collection. An example will make this clear. Suppose we want to include orders filtered by some property of Customer:
context.Customers.Include(c => c.Orders.Where(o => o.Classification == c.Classification))
It compiles, but it'll throw a very technical runtime exception, basically telling that o.Classification == c.Classification can't be translated because c.Classification can't be found. The query has to be rewritten using a back-reference from Order to Customer:
context.Customers.Include(c => c.Orders.Where(o => o.Classification == o.Customer.Classification))
The predicate o => o.Classification == o.Customer.Classification) is "stand alone" in the sense that it can be used to filter Orders independently:
context.Orders.Where(o => o.Classification == o.Customer.Classification) // No one would try 'c.Classification' here
This restriction may change in later EF versions than the current stable version (EF core 5.0.7).
What can (not) be filtered
Since Where is an extension method on IEnumerable it's clear that only collections can be filtered. It's not possible to filter reference navigation properties. If we want to get orders and only populate their Customer property when the customer is active, we can't use Include:
context.Orders.Include(o => o.Customer.Where( ... // obviously doesn't compile
Filtered Include vs filtering the query
Filtered Include has given rise to some confusion on how it affects filtering a query as a whole. The rule of the thumb is: it doesn't.
The statement...
context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))
...returns all customers from the context, not only the ones with undeleted orders. The filter in the Include doesn't affect the number of items returned by the main query.
On the other hand, the statement...
context.Customers
.Where(c => c.Orders.Any(o => !o.IsDeleted))
.Include(c => c.Orders)
...only returns customers having at least one undeleted order, but having all of their orders in the Orders collections. The filter on the main query doesn't affect the orders per customer returned by Include.
To get customers with undeleted orders and only loading their undeleted orders, both filters are required:
context.Customers
.Where(c => c.Orders.Any(o => !o.IsDeleted))
.Include(c => c.Orders.Where(o => !o.IsDeleted))
Filtered Include and projections
Another area of confusion is how filtered Include and projections (select new { ... }) are related. The simple rule is: projections ignore Includes, filtered or not. A query like...
context.Customers
.Include(c => c.Orders)
.Select(c => new { c.Name, c.RegistrationDate })
...will generate SQL without a join to Orders. As for EF, it's the same as...
context.Customers
.Select(c => new { c.Name, c.RegistrationDate })
It gets confusing when the Include is filtered, but Orders are also used in the projection:
context.Customers
.Include(c => c.Orders.Where(o => !o.IsDeleted))
.Select(c => new
{
c.Name,
c.RegistrationDate,
OrderDates = c.Orders.Select(o => o.DateSent)
})
One might expect that OrderDates only contains dates from undeleted orders, but they contain the dates from all Orders. Again, the projection completely ignores the Include. Projection and Include are separate worlds.
How strictly they lead their own lives is amusingly demonstrated by this query:
context.Customers
.Include(c => c.Orders.Where(o => !o.IsDeleted))
.Select(c => new
{
Customer = c,
OrderDates = c.Orders.Select(o => o.DateSent)
})
Now pause for a moment and predict the outcome...
The not so simple rule is: projections don't always ignore Include. When there is an entity in the projection to which the Include can be applied, it is applied. That means that Customer in the projection contains its undeleted Orders, whereas OrderDates still contains all dates. Did you get it right?
Not doable.
There is an on-going discussion about this topic:
https://github.com/aspnet/EntityFramework/issues/1833
I'd suggest to look around for any of the 3rd party libraries listed there, ex.: https://github.com/jbogard/EntityFramework.Filters
You can also reverse the search.
{
var blogs = context.Author
.Include(author => author.posts)
.ThenInclude(posts => posts.blogs)
.Where(author => author == "me")
.Select(author => author.posts.blogs)
.ToList();
}
Not sure about Include() AND ThenInclude(), but it's simple to do that with a single include:
var filteredArticles =
context.NewsArticles.Include(x => x.NewsArticleRevisions)
.Where(article => article.NewsArticleRevisions
.Any(revision => revision.Title.Contains(filter)));
Hope this helps!
Although it's (still in discussion) not doable with EF Core, I've managed to do it using Linq to Entities over EF Core DbSet. In your case instead of:
var blogs = context.Blogs
.Include(blog => blog.Posts)
.ThenInclude(post => post.Author)
.ToList()
.. you'll have:
await (from blog in this.DbContext.Blogs
from bPost in blog.Posts
from bpAuthor in bPost.Author
where bpAuthor = "me"
select blog)
.ToListAsync();
I used below package
Use Z.EntityFramework.Plus
IncludeFilter and IncludeFilterByPath two methods are which you can use.
var list = context.Blogs.IncludeFilter(x => x.Posts.Where(y => !y.IsSoftDeleted))
.IncludeFilter(x => x.Posts.Where(y => !y.IsSoftDeleted)
.SelectMany(y => y.Comments.Where(z => !z.IsSoftDeleted)))
.ToList();
Here is the example https://dotnetfiddle.net/SK934m
Or you can do like this
GetContext(session).entity
.Include(c => c.innerEntity)
.Select(c => new Entity()
{
Name = c.Name,
Logo = c.Logo,
InnerEntity= c.InnerEntity.Where(s => condition).ToList()
})
Interesting case and it worked!!
If you have table/model user(int id, int? passwordId, ICollection<PwdHist> passwordHistoryCollection) where collection is history of passwords. Could be many or none.
And PwdHistory(int id, int UserId, user User). This has a quasi relationship via attributes.
Needed to get user, with related current password record, while leaving historical records behind.
User user = _userTable
.Include(u => u.Tenant)
.Include(u => u.PwdHistory.Where(p => p.Id == p.PwdUser.PasswordId))
.Where(u => u.UserName == userName)
.FirstOrDefault();
Most interesting part is .Include(u => u.PwdHistory.Where(p => p.Id == p.PwdUser.PasswordId))
works with user and many passwords
works with user and no passwords
works with no user
We can use by extension
public static IQueryable<TEntity> IncludeCondition<TEntity, TProperty>(this IQueryable<TEntity> query, Expression<Func<TEntity, TProperty>> predicate, bool? condition) where TEntity : class where TProperty : class
{
return condition == true ? query.Include(predicate) : query;
}
Usage;
_context.Tables.IncludeCondition(x => x.InnerTable, true)
This task can be accomplished with two queries. For example:
var query = _context.Employees
.Where(x =>
x.Schedules.All(s =>
s.ScheduleDate.Month != DateTime.UtcNow.AddMonths(1).Month &&
s.ScheduleDate.Year != DateTime.UtcNow.AddMonths(1).Year) ||
(x.Schedules.Any(s =>
s.ScheduleDate.Month == DateTime.UtcNow.AddMonths(1).Month &&
s.ScheduleDate.Year == DateTime.UtcNow.AddMonths(1).Year) &&
x.Schedules.Any(i => !i.ScheduleDates.Any())));
var employees = await query.ToListAsync();
await query.Include(x => x.Schedules)
.ThenInclude(x => x.ScheduleDates)
.SelectMany(x => x.Schedules)
.Where(s => s.ScheduleDate.Month == DateTime.UtcNow.AddMonths(1).Month &&
s.ScheduleDate.Year == DateTime.UtcNow.AddMonths(1).Year).LoadAsync();

Entity Framework Include and Select are gettind some other entities

I am using Entity Framework 6 latest stable version with Northwind database. And I wrote a query like below. Even I didn't include customer to order. And also included entity OrderDetails has Order (this is like recursion). And the final included entity Product of OrderDetails has Category even I didn't include. The weird thing is Supplier is a navigation property but it is null for product.
Plus : LazyLoading and ProxyCreationEnabled is false
var orders = Context.Orders
.Include(i => i.Order_Details)
.Include(i => i.Order_Details.Select(a => a.Product))
.Where(i => i.EmployeeID == employeeId && i.CustomerID == customerId)
.ToList();
And OrderDetail's issue
What I can't catch on this?
you can put a select if you dont want unwanted data from table.you can do by anonymous select or using view-model Class
var orders = Context.Orders
.Include(i => i.Order_Details)
.Include(i => i.Order_Details.Select(a => a.Product))
.Where(i => i.EmployeeID == employeeId && i.CustomerID == customerId)
.Select new
{
//here you can select the fields which all are you required
}
or
.Select new requireddatavm
{
//here you can select the fields which all are you required
}
In method syntax, with other operators:
.Where(i => i.EmployeeID == employeeId && i.CustomerID == customerId)
, returns the whole row including included fields

EF 7 Include a collection with where clause and then a collection one level down

I am trying to query a collection and child collection using EF 7. Here's the code:
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID )
.Include(g => g.Address.Where(p => p.AddressTypeID == 1))
.ThenInclude(p=> p.City)
.ToListAsync();
> Error CS1061 'IEnumerable<Address>' does not contain a definition for
> 'City' and no extension method 'City' accepting a first argument of
> type 'IEnumerable<Address>' could be found (are you missing a using
> directive or an assembly reference?) Contacts.DNX 4.5.1, Contacts.DNX
> Core 5.0
It works fine when I just use
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID )
.Include(g => g.Address)
.ThenInclude(p=> p.City)
.ToListAsync();
But this will load all the addresses for the customer where I only want the recent address for which the AddressTypeID is 1.
Any idea how to do this?
You can try anonymous projection, that will fetch translate your query into SQL.
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID)
.Select(cntct=> new
{
contact = cntct,
address = cntct.Address.Where(p => p.AddressTypeID == 1),
city = cntct.Address.Where(p => p.AddressTypeID == 1)
.Select(h=>h.City),
}.ToList();
You can't filter in Include. In any version of entity framework.
If you need to load a subset of the collection then you need to Join instead of using navigation property and filter whenever you need using Where clause
Like this (simplified, extra steps for readability):
var filteredAddresses = Addresses.Where(x=>x.AddressTypeId==1);
var customersWithAddress = Customers.Join(filteredAddresses, x=>x.Id,x=>x.CustomerId,(c,a)=> new {
Customer=c,
Address=a
});
Or if you need a single customer, assuming you have Customer navigation property in Address:
var addressWithCustomer = Addresses
.Where(x=>x.AddressTypeId==1 && x.CustomerId == customerId)
.Include(x=>x.Customer)
.Include(x=>x.City)
.Single();
A lot of times, it is better to approach queries which involve conditional nested entities, to start with the nested entity, apply the conditions to this nested fellow and then project out the parent entity, since it is always easier to reach to the parent entities from the nested enumerable ones. (many to one)
in our case, we can apply the filter out on the Address entity and then group it on the Contact entity.
var customerID = 86795;
var query = await _context.Addresses
.Where(a => a.Contact.CustomerID == customerID
&& a.Contact.RegistrationDate.Year == 2016
&& a.AddressTypeID == 1)
.Include(a => a.Contact)
.Include(a => a.City)
.GroupBy(a => a.Contact)
.Take(20) // by the way, you should apply some orderby for a predicatble Take
.ToListAsync();
and if you absolutely want a list of Contacts as the output of the above query, you can do this.
var contacts = query.Select(g =>
{
g.Key.Addresses = g.ToList();
return g.Key;
}).ToList();
// now you can work off the Contacts list, which has only specific addresses
This will basically give you a grouped list of all Contacts with CustomerID, and with those address types and registration years only. The important thing here is to iterate through the group to get the addresses, and not use the grouping.Key.Addresses navigation. (grouping.Key will be the Contact entity)
Also, I don't know if CustomerID is a primary key on the Contact entity, but if it is, it looks like you would just need a list of matching addresses for one Contact. In that case, the query would be:
var query = await _context.Addresses
.Where(a => a.Contact.CustomerID == customerID && a.AddressTypeID == 1)
.Include(a => a.Contact)
.Include(a => a.City)
.ToListAsync();
Include The Collection for Eager Load then use Any instead of Where ... to Select specific items in the child of the wanted entity.
var customerID = 86795;
var query = await _context.Contacts
.Where(g => g.CustomerID == customerID )
.Include(g => g.Address.Any(p => p.AddressTypeID == 1))
.ThenInclude(p=> p.City)
.ToListAsync();

Categories

Resources