【问题标题】:Does EF6 virtual navigation property result into SQL query?EF6 虚拟导航属性是否会导致 SQL 查询?
【发布时间】:2017-12-19 10:11:52
【问题描述】:

假设我有一个具有如下虚拟导航属性的实体:

public class School
{
    public long Id { get; set; }

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

据我了解,EF6 使用代理来启用 Students 的延迟加载,但执行以下 LINQ 查询:

var myStudent = this.Students.Single(x => x.Id == id);
var studentsCount = this.Students.Count();
var bestStudents = this.Students
    .OrderByDescending(x => x.GPA)
    .Take(5)
    .ToArray();

导致 SQL 查询(就像IQueryable&lt;T&gt; 一样)?或者它只是一个延迟加载集合,在第一次请求后将所有学生加载到内存中,然后执行简单的IEnumerable&lt;T&gt; LINQ 行为?

【问题讨论】:

  • 这取决于...尽管您确实不应该让您的实体类包含额外的代码 - 如果您尝试访问学生集合,您可能会遇到问题,特别是在上下文已被处理的情况下稍后。
  • @DavidG 谢谢!我的问题听起来太宽泛了 :) 我已经把它编辑掉了。
  • 第二个代码片段中的“this”是什么?

标签: c# linq entity-framework-6 navigation-properties


【解决方案1】:

当您在 Entity Framework 中查询实体时,返回的对象(总是)不是您认为的对象类型。在幕后,它创建了一个继承自您的类的全新类。因为 OOP 允许将子类存储在类型为超类的变量中,所以您永远不会真正注意到。这是您提到的“代理”。这就是virtual 函数允许延迟加载的原因。子类覆盖您的虚拟方法,并包含在返回之前延迟加载额外数据的代码。

然后,该覆盖的属性调用将检查上下文以查看导航属性是否已加载。如果是,它只会返回它们。如果不是,它将进行额外的 SQL 调用来加载它们,并将它们存储在 DbContext 中以备后用。


在您更新的问题中,我的理解是运行这些代码行会导致执行 3 个单独的查询。

【讨论】:

    【解决方案2】:

    公共虚拟属性在 EF6 中是延迟加载的。 您可以禁用 DbContext 的延迟加载,或使用 IQueryable 上的 .Include() 方法将属性包含在第一个查询中。

    http://www.entityframeworktutorial.net/EntityFramework4.3/lazy-loading-with-dbcontext.aspx

    https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx

    一旦您“迭代”通过列表(例如,通过调用 .Single()、.Count() 或 .ToArray() 方法),就会执行查询,并且您有一个学生的内存列表。有关查询执行的更多详细信息,请参阅https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/query-execution

    第一个示例将产生 3 个查询,其中第一个返回具有给定 ID 的学生,第二个返回学生数量,第三个返回按 GPA 属性排序的前 5 个学生。

    关于 DDD => 您可以使用一些分层架构,包括 ApplicationServices 和 DomainServices,其中 DomainServices 执行域逻辑,ApplicationServices 加载/转换数据。

    https://aspnetboilerplate.com/ 模板是例如域驱动设计的良好起点。

    【讨论】:

      【解决方案3】:

      假设第二个代码块在“School”实例范围内的某个地方执行(因此“this”是“School”的一个实例 - 在我下面的 school19 示例中),则可能出现以下情况:

      A) 您已经像这样加载了您的“学校”实例(启用延迟加载):

      var school19 = dbContext.Set<School>()
       .FirstOrDefault(school => school.Id == 19)
      

      那么您用于访问“学生”属性的 3 行代码将在

      时触发一个额外的数据库命中
      var myStudent = this.Students.Single(x => x.Id == id);
      

      已执行,但后续两条语句不会再发生数据库命中。

      B) 如果您像这样加载了“学校”实例(启用延迟加载):

      var school19 = dbContext.Set<School>()
       .Include(school => school.Students)
       .FirstOrDefault(school => school.Id == 19)
      

      那么您用于访问“学生”属性的 3 行代码将不会触发额外的数据库命中。

      C) 如果延迟加载被禁用,那么

      • A) 将导致空引用异常
      • B) 行为相同

      最后一点,如果“this”是对 DBContext 类实例的引用,它有一个属性

      public Set<School> Schools { get; set; }
      

      那么它将触发 3 个不同的数据库调用。但结果是不同的,因为这将在所有学校的背景下执行,而我上面的假设只适用于一所学校。

      【讨论】:

      • 如果您查询单个 ID,为什么 var bestStudents = this.Students.OrderByDescending(x =&gt; x.GPA).Take(5).ToArray(); 不会触发额外的数据库命中?我必须对此进行测试,但我认为 EF 不能缓存上一次调用中的任何内容,因为查询中有一个 OrderByDescending。
      • 一旦您访问延迟加载的属性,它将触发数据库调用来解析学生并将列表分配给属性。该属性的后续使用将在您全部加载后保存在内存中。在我看来,这也是延迟加载的原因(也是一个问题)。假设您有一所拥有 10,000 名学生的学校,一旦您访问该属性,您就会从数据库中加载 10,000 行。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-23
      • 2013-11-18
      • 2016-05-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-17
      相关资源
      最近更新 更多