实体框架在提取的数据上延迟加载意外的属性填充

时间:2018-09-21 04:54:15

标签: c# entity-framework lazy-loading

请参见以下简化示例:

学生班:

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 navigation属性

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

1 个答案:

答案 0 :(得分:2)

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

这在Entity Framework docs中有解释:

  

跟踪行为控制Entity Framework Core是否将有关实体实例的信息保留在其更改跟踪器中。如果跟踪了一个实体,则在SaveChanges()期间,该实体中检测到的任何更改都将保留到数据库中。实体框架核心还将修复从跟踪查询获得的实体与先前加载到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 ,因此似乎没有发生。