The current setup of EF in my application is lazy loading, which is great for the most part. However, I am lost trying work out how to load a list of related entities based on their IsEnabled bit attribute.
In this example I am just returning a list of entities.
return Context.Entities.ToList()
Let's say the Entities object contains a list of ChildEntities like so:
public class Entities
{
private string EntityName;
private List<ChildEntities> ChildEntities;
}
public class ChildEntites
{
private string ChildEntityName;
private bool IsEnabled;
}
I want to only want to get out the ChildEntities based on their IsEnabled flag when loading the list of Entities.
You can use the Include() method to load all child entities and then select only those which are enabled like
Context.Entities.Include("ChildEntites").Select(c => e.IsEnabled == true)
Another way would be to get the filter entities and then run the query as shown in this post
var data = from e in Context.Entities
select new
{
Entities = e,
Childs = e.ChildEntites.Where(c => c.IsEnabled == true)
};
var Results = data.ToArray().Select(x => x.Entities);
I think there is no way to filter when you load related entities in case that you use lazy loading or eager loading unless you project your query to an anonymous type or a DTO, but if you have an entity instance,you can load related entities based on a condition using explicit loading:
var entity=context.Entities.FirstOrDefault();
context.Entry(entity)
.Collection(b => b.ChildEntities)
.Query()
.Where(ce => ce.IsEnabled == true)
.Load();
If that doesn't satisfy what you are trying to achieve because you need to load the entire entity collection, then, as I said before, you should project your query to a custom class or an anonymous type:
var query= from e in Context.Entities.Include(c=>c.ChildEntities)
select new EntityDTO
{
EntityName= e.EntityName,
ChildEntites= e.ChildEntites.Where(c => c.IsEnabled == true)
};
Using a projection
var entities = context.Entities
.Select(x => new {x, x.ChildEntities.Where(y => y.IsEnabled))
.ToList() // resolve from database before selecting the main entity
.Select(x => x.x);
Using a third party library
EF+ Query IncludeFilter allow you to easily filter related entities
var entities = context.Entities.IncludeFilter(x => x.ChildEntities.Where(y => y.IsEnabled))
.ToList();
You can find the documentation here
Disclaimer: I'm the owner of the project EF+.
A couple ways I would recommend this approach. I would either lazy load where IsEnabled = false and eager load in a separate call where IsEnabled = true OR once you have the lazy loaded collection, in a separate call, get the children where IsEnabled = true. I don't believe you will be able to do this in one call. The other option would be a stored procedure. I hope this helps.
I had a similar problem. I solved it in the following manner.
Create a new method in your model Entities. Lets call it ChildEntitiesEnabled
public ICollection<ChildEntity> ChildEntitiesEnabled()
{
//First I get the full list using the lazy loading...
var allChildEntities=ChildEntities.ToList();
//do further processing if there is data
if(allChildEntities!=null && allChildEntities.Count()>0)
{
var childEntitiesEnabled = ChildEntities.Where(x=>x.Enabled==true).ToList();
return childEntitiesEnabled;
}
return null; //or you can return an empty list...
}
I like this method because you can use it anywhere the model is available without complicated code strewn all over the place. Also, you dont lose all the ChildEntities data... that is available too from the original call.
Related
Most of the examples I see on the internet show the navigation properties as either ICollection or straight List implementation. They are usually virtual, to enable lazy-loading.
However, when you access such property, it will load the entire collection in memory and if you have a subquery after it (i.e. object.MyListProperty.Where(...)) I have noticed that an SQL query will be issued for each item in the MyListProperty.
How do I avoid this? I want the where clause after the list property to execute on the SQL server, if possible. Can I use an IQueryable navigation property? Is there any best-practice for such case?
My advice for best practise is to disable Lazy loading altogether. Instead force the caller to eagerly load navigation properties through include statements or by using projections.
There are 3rd party products that support include with filters, as described in this post: How to filter include entities in entity framework, but in my experience this further complicates down-stream processing of the objects that are retrieved. If the entity object is loaded outside of method X, because method X can't know for sure if the navigation properties have been loaded with the correct filters, method X starts off by re-querying for the precise rows that it knows it needs.
using (var context = new MyDbContext())
{
context.Configuration.LazyLoadingEnabled = false;
// TODO: load your data
...
}
In this way the records will only be loaded when they are explicitly requested.
When you want access to an IQueryable so you can defer the loading of the data, then make those queries against the DbContext instance and not from the object.
In this example assume that a Customer has many thousands of transactions, so we don't want them to be eagerly or lazy loaded at all.
using (var context = new MyDbContext())
{
context.Configuration.LazyLoadingEnabled = false;
var customer = context.Customers.First(x => x.Id == 123);
...
// count the transactions in the last 30 days for this customer
int customerId = customer.Id;
DateTime dateFrom = DateTime.Today.AddDays(-30)
// different variations on the same query
int transactionCount1 = context.Customers.Where(x => x.Id == customerId)
.SelectMany(x => x.Transactions.Where(x => x.TransactionDate >= dateFrom))
.Count();
int transactionCount2 = context.Customers.Where(x => x.Id == customerId)
.SelectMany(x => x.Transactions)
.Where(x => x.TransactionDate >= dateFrom)
.Count();
int transactionCount3 = context.Transactions.Where(x => x.CustomerId == customerId)
.Where(x => x.TransactionDate >= dateFrom)
.Count();
}
It is good that you have identified that you want to use an IQueryable<T> we access them from the DbContext directly, not from the instances that were previously retrieved.
This works:
using (var dbContext = new SmartDataContext())
{
dbContext.Configuration.ProxyCreationEnabled = false;
var query = dbContext.EntityMasters.OfType<Person>();
if (includeAddress)
query.Include(p => p.Addresses);
if (includeFiles)
query.Include(p => p.FileMasters);
output.Entity = query.Include(s=>s.Addresses).FirstOrDefault<Person>(e => e.EntityId == id);
}
while this doesn't:
using (var dbContext = new SmartDataContext())
{
dbContext.Configuration.ProxyCreationEnabled = false;
var query = dbContext.EntityMasters.OfType<Person>();
if (includeAddress)
query.Include(p => p.Addresses);
if (includeFiles)
query.Include(p => p.FileMasters);
output.Entity = query.FirstOrDefault<Person>(e => e.EntityId == id);
}
I am trying to include Addresses, Files based on boolean flags coming from function. However it seems, EF not including them when using IF condition.
This is related to my previous question which actually worked using Include.
You need to assign the result of Include back to query
query = query.Include(p => p.Addresses);
Entity framework's 'Include' function only works when it is connected to the entire linq query that was looking up the entity. This is because the linq query is actually a form of Expression that can be inspected as a whole before it is executed.
In the second example there the Person object is already detached from the database so EF has no information on which table Person came from and how it should join Person with the address table to get the results you want.
If you turn on dynamic proxy generation EF is able to keep track of the relation between the entity and the database. However, I'm not sure if this will make the include statement work.
I have the following model:
A User has a collection of Photos. In the Photo model, there is a property called IsProfilePhoto.
When I do the following, the results are not as expected.
var user = dbContext.Users.SingleOrDefault(u => u.Id == 1);
var profilePhoto = user.Photos.SingleOrDefault(p => p.IsProfilePhoto);
With lazy loading on, this performs two queries.
The first one gets the user by id as expected.
The second one however, gets the collection of photos by user id and then in memory does the match on IsProfilePhoto.
I was hoping that with lazy loading on it would add the SingleOrDefault to the query as well.
Is this just not possible and I must always do the inverse? E.g.
var profilePhoto = dbContext.Photos.SingleOrDefault(p => p.UserId == 1 && p.IsProfilePhoto);
var user = profilePhoto.User;
I get the reasoning, there are just certain reasons why it's more convenient to go from the User to get the profile photo.
You can get the result with a single database query by using a projection:
var userWithProfilePhoto = dbContext.Users
.Where(u => u.Id == 1)
.Select(u => new
{
User = u,
ProfilePhoto = u.Photos.Where(p => p.IsProfilePhoto).FirstOrDefault()
})
.SingleOrDefault();
userWithProfilePhoto.User and userWithProfilePhoto.ProfilePhoto are the two entities you are looking for.
You have to use Eagerly loading to load multiple levels. Lazy load, loads the level when you access this.
var user = dbContext.Users.Include(u => u.Photos).SingleOrDefault(u => u.Id == 1);
var profilePhoto = user.Photos.SingleOrDefault(p => p.IsProfilePhoto);
This is the subtle difference in LINQ methods.
You can do the filtering as part of the query as:
var profilePhoto = user.Photos.Where(p => p.IsProfilePhoto).SingleOrDefault();
Due to this behavior in LINQ to Entities, I try to always use the Where method for the condition and the parameterless overloads for First, Single, FirstOrDefault, SingleOrDefault, Any, and Count.
Edit:
My bad, MSDN mentions it directly, but any reference to a navigation property (when lazy loading is enabled) loads all of the related records.
My best suggestion then is to a) accept extra database access, or b) query as you did in your alternative example, with the first table being the 'many' of the 'one-to-many' relationship.
First, and FirstOrDefault are lazy loaded, Single, and SingleOrDefault eagerly loaded. If you don't need a an exception thrown in case of several items returned by the query, you can change it to FirstOrDefault.
I have a gridview, the datasource of which is the following function:
public static List<Train> GetTrainsByIDs(int [] ids) {
using (var context = new MyEntities())
{
return ids.Select(x => context.Trains.Single(y => y.TrainID ==x)).AsQueryable().Include(x=>x.Station).ToList();
}
}
The grid view has an ItemTemplate of <%# Eval("Station.Name") %>.
This causes the error The ObjectContext instance has been disposed and can no longer be used for operations that require a connection despite the fact that I used the include method.
When I change the function to
public static List<Train> GetTrainsByIDs(int [] ids) {
using (var context = new MyEntities())
{
return context.Trains.Where(x => ids.Contains(x.TrainID)).Include(x=>x.Station).ToList();
}
}
it works fine, but then they come out in the wrong order, and also if I have 2 ids the same I would like 2 identical trains in the list.
Is there anything I can do other than create a new viewmodel? Thank you for any help
As for the first query: that's deferred execution.You created an IEnumerable of Trains, noticed that it did not have the Include method, so cast it to IQueryable, added the Include and added the ToList() to prevent lazy loading.
But As per MSDN on DbExtensions.Include:
This extension method calls the Include(String) method of the IQueryable source object, if such a method exists. If the source IQueryable does not have a matching method, then this method does nothing.
(emphasis mine)
The result of the select is an IEnumerable converted to IQueryable, but now implemented by EnumerableQuery which does not implement Include. And nothing happens.
Now the data enters the grid which tries to display the station, which triggers lazy loading while the context is gone.
Apart from that, this design has another flaw: it fires a query for each id separately.
So the second query is much better. It is one query, including the Stations. But now the order is dictated by the order the database pleases to return. You could use Concat to solve this:
IQueryable<Train> qbase = context.Trains.Include(x=>x.Station);
IQueryable<Train> q = null;
foreach (var id in ids)
{
var id1 = id; // Prevent modified closure.
if (q == null)
q = qbase.Where(t => t.Id == id1);
else
q = q.Concat(qbase.Where (t => t.Id == id1));
}
The generated query is not very elegant (to say the least) but after all it is one query as opposed to many.
After reading #Gert Arnold's answer, and getting the idea of doing it in 2 stages, I managed very simply using the first query like this:
using (context = new MyEntities())
{
var trns = context.Trains.Include(x => x.Station);
return ids.Select(x => trns.Single(y => y.TrainID == x)).ToList();
}
So I have this query in my repository (also using Unit of Work pattern) which uses eager loading to make one hit to the database:
from g in _context.Games.Include(pg => pg.PreviousGame).Include(go => go.GameObjects)
where EntityFunctions.DiffMilliseconds(DateTime.Now, g.EndDate) > 0
&& g.GameTypeId == (int)GameTypes.Lottery
&& g.GameStatusId == (int)GameStatues.Open
select new LotteryModel
{
EndDate = g.EndDate,
GameId = g.Id,
PreviousGameEndDate = g.PreviousGame.EndDate,
PreviousGameId = g.PreviousGameId.HasValue ? g.PreviousGameId.Value : 0,
PreviousGameStartDate = g.PreviousGame.StartDate,
PreviousWinningObjectCount = g.PreviousGame.GameObjects.Select(go => go.Object.Count).FirstOrDefault(),
PreviousWinningObjectExternalVideoId = g.PreviousGame.GameObjects.Select(go => go.Object.Video.ExternalVideoId).FirstOrDefault(),
PreviousWinningObjectName = g.PreviousGame.GameObjects.Select(go => go.Object.Video.Name).FirstOrDefault(),
StartDate = g.StartDate,
WinningObjectCount = g.GameObjects.Select(go => go.Object.Count).FirstOrDefault(),
WinningObjectExternalVideoId = g.GameObjects.Select(go => go.Object.Video.ExternalVideoId).FirstOrDefault(),
WinningObjectName = g.GameObjects.Select(go => go.Object.Video.Name).FirstOrDefault()
};
However I'm reluctant to use this because I now have to create a separate LotteryModel object to return up throughout my other layers.
I would like to be able to return an entity of type "Game" which has all of the navigational methods to all of my other data (PreviousGame, GameObjects, etc) and then map the needed properties to my flat view model, but when I do this it seems to only lazy load the objects and then I have the additional hits to the DB.
Or do I have this wrong and whenever I need to return heirarchical data I should return it through my LINQ query in the select portion?
My basic goal is to reduce the hits to the DB.
I don't really understand the problem. You return your Games object and you can access the properties and subobjects off it. Your use of the Include() method tells it to load what you need, and not lazy load it.
Make sure you return a single object via a .First, .FirstOrDefault, .Single, .SingleOrDefault, or similar methods.
I ended up with this query (FYI I'm using the System.Data.Objects namespace for the Include extension):
(from g in _context.Games.Include(pg => pg.PreviousGame.GameObjects.Select(o => o.Object.Video))
.Include(go => go.GameObjects.Select(o => o.Object.Video))
where EntityFunctions.DiffMilliseconds(DateTime.Now, g.EndDate) > 0
&& g.GameTypeId == (int)GameTypes.Lottery
&& g.GameStatusId == (int)GameStatues.Open
select g).FirstOrDefault();
I guess I just needed to include more of the heirarchy and didn't know I could use Select() in the Include() function!