I'm using Code First in EF. Let's say I have two entities:
public class Farm
{
....
public virtual ICollection<Fruit> Fruits {get; set;}
}
public class Fruit
{
...
}
My DbContext is something like this:
public class MyDbContext : DbSet
{
....
private DbSet<Farm> FarmSet{get; set;}
public IQueryable<Farm> Farms
{
get
{
return (from farm in FarmSet where farm.owner == myowner select farm);
}
}
}
I do this so that each user can only see his farms, and I don't have to call the Where on each query to the db.
Now, I want to filter all the fruits from one farm, I tried this (in Farm class):
from fruit in Fruits where fruit .... select fruit
but the query generated doesn't include the where clause, which is very important because I have dozens of thousands of rows and is not efficient to load them all and filter them when they're Objects.
I read that lazy loaded properties get filled the first time they're accessed but they read ALL the data, no filters can be applied UNLESS you do something like this:
from fruits in db.Fruits where fruit .... select fruit
But I can't do that, because Farm has no knowledge of DbContext (I don't think it should(?)) but also to me it just loses the whole purpose of using navigation properties if I have to work with all the data and not just the one that belongs to my Farm.
So,
- am I doing anything wrong / making wrong assumptions?
- Is there any way I can apply a filter to a navigation property that gets generated to the real query? (I'm working with a lot of data)
Thank you for reading!
Unfortunately, I think any approach you might take would have to involve fiddling with the context, not just the entity. As you've seen, you can't filter a navigation property directly, since it's an
ICollection<T>
and not anIQueryable<T>
, so it gets loaded all at once before you have a chance to apply any filters.One thing you could possibly do is to create an unmapped property in your
Farm
entity to hold the filtered fruit list:And then, in your context/repository, add a method to load a
Farm
entity and populateFilteredFruits
with the data you want:That should populate
myFarm.FilteredFruits
with only the filtered collection, so you could use it the way you want within your entity. However, I haven't ever tried this approach myself, so there may be pitfalls I'm not thinking of. One major downside is that it would only work withFarm
s you load using that method, and not with any general LINQ queries you perform on theMyDbContext.Farms
dataset.All that said, I think the fact that you're trying to do this might be a sign that you're putting too much business logic into your entity class, when really it might belong better in a different layer. A lot of the time, it's better to treat entities basically as just receptacles for the contents of a database record, and leave all the filtering/processing to the repository or wherever your business/display logic lives. I'm not sure what kind of application you're working on, so I can't really offer any specific advice, but it's something to think about.
A very common approach if you decide to move things out the
Farm
entity is to use projection:...and then use the generated anonymous objects for whatever you want to do, rather than trying to add extra data to the
Farm
entities themselves.Lazy loading doesn't support filtering; use filtered explicit loading instead:
The explicit loading approach requires two round trips to the database, one for the master and one for the detail. If it is important to stick to a single query, use a projection instead:
EF always binds navigation properties to their loaded entities. This means that
farm.Fruit
will contain the same filtered collection as theFruit
property in the anonymous type. (Just make sure you haven't loaded into the context any Fruit entities that should be filtered out, as described in Use Projections and a Repository to Fake a Filtered Eager Load.)Just figured I'd add another solution to this having spent some time trying to append DDD principles to code first models. After searching around for some time I found a solution like the one below which works for me.
The idea is that the farms fruit collection is not directly accessible but is instead exposed through a property that pre-filters it. The compromise here is the static expression that is required to be able to address the fruit collection when setting up mapping. I've started to use this approach on a number of projects where I want to control the access to an objects child collections.