【问题标题】:Entity Framework: Navigation Properties Issue实体框架:导航属性问题
【发布时间】:2013-12-03 17:14:27
【问题描述】:

我正在使用实体框架代码优先,我有一个类Course,它有一个导航属性Students

public virtual Collection<Student> Students { get; set;}

它工作正常,但是当我访问此导航属性时,所有数据都从数据库中检索:

var allStudents = course.Students; // Here it retrieves the data
var activeStudents = allStudents.Where(n => n.Active); // Here it filter the data on memory
var listOfActiveStudents = activeStudents.ToList(); // It already has the data on memory.

您可以想象,我需要在执行 .ToList() 时执行查询,因为我不想从数据库中获取所有 Students,而只获取活动的。

你知道我做错了什么吗?

【问题讨论】:

    标签: .net entity-framework ef-code-first dbcontext


    【解决方案1】:

    延迟加载将整个集合加载到内存中。如果您不想这样,请通过删除 virtual 关键字来关闭延迟加载,并在 DbEntry 上使用 Query 对象:

    public GetCourseWithActiveStudentsLoaded(int courseid)
    {
       var course= context.Courses.Find(courseid); 
    
       context.Entry(course)
              .Collection(c => c.Students)
              .Query()
              .Where(s => s.Active)
              .Load();
    
       return user
    }
    

    “活动”标志是否表明您正在尝试实施软删除?如果是这样,这里有一个解决方案:Soft Delete in Entity Framework

    您可以在此处为过滤后的包括投票:Allow filtering for Include extension method

    另一种方法是继承。你可以有一个ActiveStudent 继承自Student 和一个ActiveStudents 导航属性以及Course 类中的AllStudents 导航属性

    public virtual Collection<Student> AllStudents { get; set;}
    public virtual Collection<ActiveStudent> ActiveStudents { get; set;}
    

    参考:

    Applying filters when explicitly loading related entities:

    【讨论】:

    • .Query()方法的user.Students的类型是什么?
    • “活动”字段就是一个例子。我必须应用更复杂的过滤器。我试过删除“虚拟”,它没有运行查询......但是当“ToList()”执行时它没有带来任何东西(它不执行任何查询)
    • @Moho 啊。我的错;-(我在这里的级别错误。我需要DbCollectionEntry
    • @ArielScherman 当您关闭延迟加载时,您必须显式加载。看看如何在我的答案中包含的链接上急切加载。
    • 为避免急切加载所有项目,您可以使用 .Query 方法,如@Moho 的答案、我编辑的答案以及我在答案中添加的链接中所述
    【解决方案2】:

    如果您要使用正确的变量类型,那么您会看到发生了什么。整个集合被导航属性延迟加载到内存中。

    //user is an instance of the class User referenced by DbSet<User>
    //when you lazy load a navigation property in that set, it loads the data
    ICollection<Student> allStudents = user.Students;
    
    //At this point, all of the data was lazy loaded
    //But the Where creates an IEnumerable of the in memory set
    IEnumerable<Student> activeStudents = allStudents.Where(n => n.Active);
    
    //At this point, the IEnumerable is iterated, and a List is returned
    List<Student> listOfActiveStudents = activeStudents.ToList();
    

    【讨论】:

    • 谢谢,我知道...问题是当您访问导航属性时,会在第一行检索学生。我该怎么做才能只检索活跃的学生?
    • 我的意思是...查询在第一行执行,然后在内存上应用过滤器。但我希望在执行“ToList()”时执行查询
    • @ArielScherman - 你需要一个IQueryable 来完成它。您应该使用外键关系对学生表发出查询。像db.Students.Where( s =&gt; s.Active &amp;&amp; s.UserId == user.UserId ) 这样的东西。这将为您提供一个 IQueryable 能够利用您正在寻找的延迟执行。
    • @Ariel - 那么我建议按照 Moho 的建议使用:db.Entry(user).Collection( u =&gt; u.Students ).Query().Where( s =&gt; s.Active ).ToList();。这只会加载用户的活跃学生。 .Collection 返回 DbCollectionEntryQuery 返回 IQueryable,两者都不会将集合加载到内存中。使用Where 也会在此处返回IQueryable,而对ToList 的最终调用是将集合加载到内存中的位置。
    【解决方案3】:

    使用dbContext.Entry( user ).Collection( u =&gt; u.Students ).Query() 为学生集合导航属性获取IQueryable&lt;Student&gt;,此时您可以添加过滤器并在您准备好数据时进行枚举

    【讨论】:

    • 那么.. 导航属性没有解决方案吗?这怎么可能?我使用存储库模式,所以做你说的很痛苦
    • 延迟加载导航属性是一个全有或全无的操作,本质上是dbContext.Entry( user ).Collection( u =&gt; u.Students ).Load()。通过使用.Query(),您可以在从数据库加载结果之前过滤结果。我认为 repo 模式会使这变得不那么麻烦,因为您已经从开发人员那里抽象出实体的加载
    【解决方案4】:

    一种解决方法是翻转您的查询,尽管这意味着通常避免使用导航属性。 (实际实现会因您的模型而异。)

    var allStudents =
       context
       .Students
       .Where(s => s.CourseID == course.ID) // depends on your model
       .Where(s => s.Active)
       .ToList();
    

    我更喜欢使用 Entry 方法,因为我对我的模型使用通用接口并且我不想公开 EF6 类型。

    另一种避免暴露 EF6 类型的方法是编写如下方法:

    public IQueryable<TChild> Nav<TParent, TChild>(
       TParent pParent,
       Expression<Func<TParent, ICollection<TChild>>> pNavigationExpression
    ) where TParent : class
    where TChild : class =>
       Entry(pParent)
       .Collection(pNavigationExpression)
       .Query();
    

    用过这样的东西:

    var allStudents =
       context
       .Nav(course, c => c.Students)
       .Where(s => s.Active)
       .ToList()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-08-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-18
      • 1970-01-01
      • 1970-01-01
      • 2016-03-29
      相关资源
      最近更新 更多