延迟加载动态代理POCOs n + 1选择

时间:2013-03-04 03:52:43

标签: c# entity-framework entity-framework-5

考虑以下实体模型和功能:

public class Order
{
  public int OrderId {get; set;}
  public int StatusId {get; set;}

  public virtual Status OrderStatus {get; set;}
}

public class Status
{
  public int StatusId {get; set;}
  public String Name { get; set;}
}

public void ShowOrders()
{
  //load all status entities.
  //Will EF check for these in object cache first when I access order.Status for
  //the first time?
  //Or perhaps even auto include them in materialised orders?
  context.Status.Load(); 

  //enumerate orders without explicit status include
  foreach(Order o in context.Orders.ToList())
  {
    //Get Status navigation property for each order
    //Will database be hit?
    Console.WriteLine("Order: {0:N}, Status: {1}", o.OrderId, o.OrderStatus.Name);
  }
}

我知道我可以明确地做:

context.Orders.Include(o=>o.OrderStatus).ToList();

在查询订单时包含状态以防止n + 1选择。我知道,如果我访问Order.OrderStatus导航属性,则在检查数据库之前检查DbReferenceEntry.IsLoaded并检索缓存的Status对象。

我想知道的是,如果父实体具体化(即使未调用DbReferenceEntry.IsLoaded),如果引用实体位于对象缓存中,则填充DbReferenceEntry.CurrentValue.Include()已经?

因此,在上面的示例中,首次访问Order.OrderStatus 时,会执行数据库查询,即使所有状态都在对象缓存中,因为先前有Status.Load()调用列举订单?

2 个答案:

答案 0 :(得分:0)

EF将在访问导航属性时首先检查对象缓存,因此如果已加载所有Status实体,则访问Order.OrderStatus不应发出数据库查询。 如果引用已经在对象缓存中,则将在实现期间填充DbReferenceEntry。

如果您担心发出的查询数量,请考虑关闭延迟加载,以便在发出自动查询的情况下,您将获得null。

有关更多性能指导,请参阅此文章:http://msdn.microsoft.com/en-us/data/hh949853.aspx

答案 1 :(得分:0)

我没有测试以下语句(通过观察何时以及哪些SQL查询实际在分析器中运行),它们只是猜测:

  

即使所有状态都在,也会执行数据库查询   对象缓存,因为在枚举之前调用了Status.Load()   订单?

如果数据库查询已执行,我很确定原因是 具体 context.Status.Load()被调用。这只是执行Statuscontext.Status.Load()数据库表的快照。在您枚举订单时,EF无法确定Status表在此期间没有更改,并且已插入其他Status行。因此,为了避免错误的数据表示,EF 必须运行新的查询 - 除非EF有其他方法来识别是否需要查询。

还有其他方法,在这种情况下,因为Order实体只有引用Status,而不是Status集合。加载Order后 - 通过枚举context.Orders.ToList() - EF将始终将外键加载到OrderStatus,无论您对Include使用Status或不。如果您没有在模型中公开FK StatusId作为属性,情况甚至会发生。在实现Order时,将运行关系修正并检查其主键与与Status一起加载的FK具有相同值的Order实体是否已存在于对象上下文。如果是,则Order.OrderStatus属性将立即设置为该Status实体 - 我猜,EF会将导航属性标记为IsLoaded。对于引用,只能有一个匹配的实体,如果该实体已经附加到上下文并分配给导航属性,则运行查询是没有意义的。

所以,我想说:如果右Status对象附加到上下文,则不运行查询。无论是运行context.Status.Load()还是仅加载此特定Status,运行任何其他可加载此Status的查询,或手动将Status附加到上下文(context.Status.Attach(...))。

如果Order.OrderStatus集合,我认为此行为必须更改。现在加载Order时,Status没有FK。相反,Status的FK为Order。如果您首先加载Status个实体 - context.Status.Load或任何其他查询 - 订单-FK将加载Status个对象。如果稍后加载Order关系,则会再次运行fixup,这次反过来:EF查看上下文中是否有任何Status个对象,其中FK引用加载的{{1} }。如果是,则会将Order添加到Status集合中。但这一次 - 大猜测 - 它无法将导航属性(Order.OrderStatus集合)标记为Order.OrderStatus,因为它无法确定真正全部 {{1之前已加载此IsLoaded的对象,或者同时未将Status的{​​{1}}添加到数据库中。

所以,我想,如果您访问Order集合,则会运行延迟加载以确保加载Status的潜在“Order个对象的剩余部分”。然后它会将集合标记为Order.OrderStatus。它可能不需要在集合中添加任何额外的Status,但查询是必要的,至少需要检查集合是否完整。