【问题标题】:Entity Framework Lazy Loading Unexpected Property Population on Extracted Data实体框架延迟加载提取数据上的意外属性填充
【发布时间】:2019-02-25 10:57:06
【问题描述】:

参见以下简化示例:

学生班:

public class Student
{
    public int StudentId { get; set; }
    public string StudentName { get; set; }

    public Grade Grade { get; set; }
}

年级:

public class Grade
{
    public int GradeId { get; set; }
    public string GradeName { get; set; }

    public ICollection<Student> Students { get; set; }
}

上下文类:

public class SchoolContext : DbContext
{
    public SchoolContext() : base()
    {
        Configuration.LazyLoadingEnabled = true;
    }

    public DbSet<Student> Students { get; set; }
    public DbSet<Grade> Grades { get; set; }
}

程序:

    static void Main(string[] args)
    {
        using (var ctx = new SchoolContext())
        {
            Grade[] grades = ctx.Grades.ToArray();
            Console.WriteLine(grades[0].Students == null);  // True - As expected
            var students = ctx.Students.ToArray();
            Console.WriteLine(grades[0].Students == null);  // False - ? Did not expect that
        }

        Console.Read();
    }

会发生以下情况:

  1. 延迟加载已启用
  2. Grades 的列表已保存到数组中
  3. 正如预期的那样,等级对象的Students 导航属性null
  4. 单独查询得到Students
  5. EF 以某种方式填满了内存中数组的Students 导航属性

如果不小心使用,这可能会给客户端带来非常昂贵的负载。 谁能解释为什么导航属性是如何填充到数组中的?

【问题讨论】:

  • 似乎没有启用延迟加载,因为它在SchoolContext 构造函数中设置为falseDbContext 跟踪您使用同一个 DbContext 实例查询的所有实体,因此我猜它会因此在后台填充相关的导航属性。
  • 作为测试,尝试更改代码以使用ctx.Grades.AsNoTracking().ToArray(); 获取成绩,并查看加载学生后grades[0].Students == null 是否仍然为假。
  • 你可以做的另一个测试:在从数据库中获取学生之前尝试插入ctx.Grades.Detach(grades[0]);,并检查grades[0].Students == null之后是否为假。
  • 感谢@Alisson,我的真实示例设置了Configuration.LazyLoadingEnabled = true;。在这里修改。
  • 另外值得一提的是:如果启用了延迟加载(这似乎是您编辑后的情况),它应该在访问导航属性Student 后立即加载(因为这是目的延迟加载)。但是,当您第一次尝试时似乎没有发生这种情况,因为您的导航属性不是virtual。也许这正是你想要的。

标签: c# entity-framework lazy-loading


【解决方案1】:

grades[0].Students 在使用 ctx.Students.ToArray(); 从您的数据库中获取学生的查询后加载的原因是您 DbContext 正在跟踪更改

这在Entity Framework docs中有解释:

跟踪行为控制 Entity Framework Core 是否将有关实体实例的信息保留在其更改跟踪器中。如果一个实体被跟踪,那么在实体中检测到的任何更改都将在 SaveChanges() 期间持久保存到数据库中。 Entity Framework Core还将修复从跟踪查询获得的实体与之前加载到 DbContext 实例中的实体之间的导航属性

这是 EF Core 文档,但这也适用于 EF6 for .NET Framework。

如果您想禁用此行为,您可以将实体加载为不跟踪:

ctx.Grades.AsNoTracking().ToArray();

...您也可以默认禁用它(例如在 DbContext 构造函数中),就像延迟加载一样。


您可以这样做的另一种方法是手动将对象从上下文中分离出来。 然后,如果您打算进行任何更改并将其持久化到数据库中,则应在查询学生之后和进行更改之前重新附加您的实体:

using (var ctx = new SchoolContext())
{
    Grade[] grades = ctx.Grades.ToArray();
    Grade firstGrade = grades[0];
    Console.WriteLine(firstGrade.Students == null);  // True - as expected

    ctx.Grades.Detach(firstGrade); // stop tracking changes for this entity
    var students = ctx.Students.ToArray();
    Console.WriteLine(firstGrade.Students == null);  // True - still null

    // Let's reattach so we can track changes and save to database
    ctx.Grades.Attach(firstGrade);
    firstGrade.GradeName = "Some new value"; // will be persisted, as this is being tracked again
    ctx.SaveChanges();
}

另外,值得一提的是,启用了 延迟加载,第一次访问 grades[0].Students 应该让 EF 加载该导航属性,如果它尚未加载(这正是它的目的),但是它似乎这没有发生,因为您的导航属性不是virtual

【讨论】:

    猜你喜欢
    • 2015-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-11
    • 2017-11-22
    • 1970-01-01
    • 2013-03-24
    相关资源
    最近更新 更多