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
Related
I am attempting to get a list of Order Ids from a EF linq query. The sql query is returning back quickly but I think the EF framework is trying to create the full entity. I only want the ID of the order. It seems that it creates the whole entity and then it parses it out to only the id. Which seems to be a complete waste of resources.
Orders are a complex object that includes lots of child entitys. I dont need anything but the Ids of the orders in the list. Orders are organized into OrderCollection which is a many to many relationship.
The basic query in English is get the order ids in the specified order collection and have a cart date newer then the date specified and only send the specified page (skip and take).
example:
_repo.Orders.Where(o => o.OrderCollection.Any(r => r.Id == RoutingRuleId)).ToList()
.Where(o => o.OrderDate >= StartDateTime)
.OrderBy(x => x.OrderDate )
.Skip(RecordsToSkipCount)
.Take(BatchSize).Select(x => new { x.Id }).ToArray();
The sql runs in just 102ms for this in debug mode. But afterwards I see the memory go to up 4GB before failing. The batchsize is only 100. Its like it grabbing everything.
I tried moving the select around but that failed also or gave syntax errors or poor performance in running the sql (SQL taking 16 seconds).
Example
_repo.Orders.Select(x => new { x.Id, x.OrderCollection, x.OrderDate})
.Where(o => o.OrderCollection.Any(r => r.Id == RoutingRuleId)).ToList()
.Where(o => o.OrderDate >= StartDateTime)
.OrderBy(x => x.OrderDate )
.Skip(RecordsToSkipCount)
.Take(BatchSize).Select(x => new { x.Id }).ToArray();
The database has millions of records.
What you have is roughly;
List<Order> list = _repo.Orders
.Where(o => o.OrderCollection.Any(r => r.Id == RoutingRuleId))
.ToList();
list.Where(o => o.OrderDate >= StartDateTime)
.OrderBy(x => x.OrderDate )
.Skip(RecordsToSkipCount)
.Take(BatchSize)
.Select(x => new { x.Id })
.ToArray();
That first .ToList is forcing EF Core to load every order with a matching routing rule into memory. The rest of the expression is then using IEnumerable extension methods to process those results.
I think you want to rearrange that to;
IQueryable<Order> query = _repo.Orders
.Where(o => o.OrderCollection.Any(r => r.Id == RoutingRuleId)
&& o.OrderDate >= StartDateTime)
.OrderBy(x => x.OrderDate )
.Skip(RecordsToSkipCount)
.Take(BatchSize)
.Select(x => new { x.Id });
query.ToArray();
Creating an IQueryable doesn't trigger EF Core to execute any SQL. An IQueryable is just a description of the query you would like to run. Then it's the .ToArray method that will finally cause EF Core to compile and execute an sql statement.
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();
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);
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();
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();