在EF Code First中过滤导航属性

edp*_*aez 10 .net c# entity-framework code-first entity-framework-5

我在EF中使用Code First.假设我有两个实体:

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

public class Fruit
{
    ...

}
Run Code Online (Sandbox Code Playgroud)

我的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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我这样做,以便每个用户只能看到他的农场,我不必调用每个查询的位置到数据库.

现在,我想过滤掉一个农场的所有水果,我试过这个(在Farm类中):

from fruit in Fruits where fruit .... select fruit
Run Code Online (Sandbox Code Playgroud)

但是生成的查询不包含where子句,这非常重要,因为我有几十万行,加载它们并在它们是对象时过滤它们效率不高.

我读到延迟加载的属性在第一次被访问时被填充,但是他们读取了所有数据,没有过滤器可以应用,除非你做这样的事情:

from fruits in db.Fruits where fruit .... select fruit
Run Code Online (Sandbox Code Playgroud)

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

所以,

  1. 我做错什么/做出错误的假设?
  2. 有没有什么办法可以将过滤器应用到为真实查询生成的导航属性?(我正在处理大量数据)

谢谢你的阅读!

Jer*_*odd 8

不幸的是,我认为你可能采取的任何方法都必须涉及摆弄上下文,而不仅仅是实体.正如您所见,您无法直接过滤导航属性,因为它是一个ICollection<T>而不是一个IQueryable<T>,所以在您有机会应用任何过滤器之前,它会立即加载.

您可能要做的一件事是在Farm实体中创建一个未映射的属性来保存过滤后的水果列表:

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

  [NotMapped]
  public IList<Fruit> FilteredFruits { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后,在您的上下文/存储库中,添加一个方法来加载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);
Run Code Online (Sandbox Code Playgroud)

这应该myFarm.FilteredFruits只包含已过滤的集合,因此您可以在实体中以您希望的方式使用它.但是,我自己从未尝试过这种方法,因此可能存在一些我没想到的陷阱.一个主要的缺点是,它只适用于Farm使用该方法加载的s,而不适用于您在MyDbContext.Farms数据集上执行的任何常规LINQ查询.

所有这一切,我认为您尝试这样做的事实可能表明您在实体类中放置了太多的业务逻辑,而实际上它可能在不同的层中更好.在很多时候,最好将实体基本上视为数据库记录内容的容器,并将所有过滤/处理留给存储库或业务/显示逻辑所在的任何地方.我不确定你正在做什么样的应用程序,所以我不能提供任何具体的建议,但这是需要考虑的事情.

如果您决定将Farm实体移出实体,那么一种非常常见的方法是使用投影:

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

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