简体   繁体   English

在EF Code First中过滤导航属性

[英]Filtering navigation properties in EF Code First

I'm using Code First in EF. 我在EF中使用Code First。 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: 我的DbContext是这样的:

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): 现在,我想过滤掉一个农场的所有水果,我试过这个(在Farm类中):

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. 但是生成的查询不包含where子句,这非常重要,因为我有几十万行,加载它们并将它们作为对象时过滤它们效率不高。

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. 但我不能这样做,因为Farm不知道DbContext(我不认为它应该(?))而且对我来说它只是失去了使用导航属性的全部目的,如果我必须处理所有数据而不仅仅是属于我的农场的人。

So, 所以,

  1. am I doing anything wrong / making wrong assumptions? 我做错什么/做出错误的假设?
  2. 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 an IQueryable<T> , so it gets loaded all at once before you have a chance to apply any filters. 如您所见,您无法直接过滤导航属性,因为它是ICollection<T>而不是IQueryable<T> ,因此在您有机会应用任何过滤器之前,它会立即加载。

One thing you could possibly do is to create an unmapped property in your Farm entity to hold the filtered fruit list: 您可以做的一件事是在Farm实体中创建一个未映射的属性来保存过滤后的水果列表:

public class Farm
{
  ....
  public virtual ICollection<Fruit> Fruits { get; set; }

  [NotMapped]
  public IList<Fruit> FilteredFruits { get; set; }
}

And then, in your context/repository, add a method to load a Farm entity and populate FilteredFruits with the data you want: 然后,在您的上下文/存储库中,添加一个方法来加载Farm实体并使用您想要的数据填充FilteredFruits

public class MyDbContext : DbContext
{
  ....    

  public Farm LoadFarmById(int id)
  {
    Farm farm = this.Farms.Where(f => f.Id == id).Single(); // or whatever

    farm.FilteredFruits = this.Entry(farm)
                              .Collection(f => f.Fruits)
                              .Query()
                              .Where(....)
                              .ToList();

    return farm;
  }
}

...

var myFarm = myContext.LoadFarmById(1234);

That should populate myFarm.FilteredFruits with only the filtered collection, so you could use it the way you want within your entity. 这应该仅使用已过滤的集合填充myFarm.FilteredFruits ,因此您可以在实体中以您希望的方式使用它。 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 with Farm s you load using that method, and not with any general LINQ queries you perform on the MyDbContext.Farms dataset. 一个主要的缺点是,它只适用于使用该方法加载的Farm ,而不适用于您在MyDbContext.Farms数据集上执行的任何常规LINQ查询。

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: 如果您决定移出Farm实体,那么一种非常常见的方法是使用投影:

var results = (from farm in myContext.Farms
               where ....
               select new {
                 Farm = farm,
                 FilteredFruits = myContext.Fruits.Where(f => f.FarmId == farm.Id && ...).ToList()
               }).ToList();

...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. ...然后将生成的匿名对象用于您想要做的任何事情,而不是尝试向Farm实体本身添加额外的数据。

Just figured I'd add another solution to this having spent some time trying to append DDD principles to code first models. 刚想到我会为此添加另一个解决方案,花了一些时间尝试将DDD原则附加到第一个模型的代码中。 After searching around for some time I found a solution like the one below which works for me. 在搜索了一段时间之后,我找到了一个类似下面的解决方案,对我有用。

public class FruitFarmContext : DbContext
{
    public DbSet<Farm> Farms { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Farm>().HasMany(Farm.FruitsExpression).WithMany();
    }
}

public class Farm
{
    public int Id { get; set; }
    protected virtual ICollection<Fruit> Fruits  { get; set; }
    public static Expression<Func<Farm, ICollection<Fruit>>> FruitsExpression = x => x.Fruits;

    public IEnumerable<Fruit> FilteredFruits
    {
        get
        {
            //Apply any filter you want here on the fruits collection
            return Fruits.Where(x => true);
        }
    }
}

public class Fruit
{
    public int Id { get; set; }

}

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. 我已经开始在许多项目中使用这种方法,我希望控制对象子集合的访问。

Lazy loading doesn't support filtering; 延迟加载不支持过滤; use filtered explicit loading instead: 使用过滤显式加载

Farm farm = dbContext.Farms.Where(farm => farm.Owner == someOwner).Single();

dbContext.Entry(farm).Collection(farm => farm.Fruits).Query()
    .Where(fruit => fruit.IsRipe).Load();

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: 如果坚持单个查询很重要,请使用投影:

Farm farm = (
    from farm in dbContext.Farms
    where farm.Owner == someOwner
    select new {
        Farm = farm,
        Fruit = dbContext.Fruit.Where(fruit => fruit.IsRipe) // Causes Farm.Fruit to be eager loaded
    }).Single().Farm;

EF always binds navigation properties to their loaded entities. EF始终将导航属性绑定到其加载的实体。 This means that farm.Fruit will contain the same filtered collection as the Fruit property in the anonymous type. 这意味着farm.Fruit将包含与匿名类型中的Fruit属性相同的过滤集合。 (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 .) (只需确保您没有将任何应该过滤掉的Fruit实体加载到上下文中,如使用预测和存储库伪造过滤的Eager Load中所述 。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM