[英]Lazy loading dynamic proxy POCOs n+1 select
考虑以下实体模型和功能:
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
将在命中数据库之前检查DbReferenceEntry.IsLoaded
并在可能的情况下检索缓存的Status对象。
我想知道的是,如果引用实体已经在对象缓存中,则在实现父实体(即使未调用.Include()
时是否填充DbReferenceEntry.IsLoaded
和DbReferenceEntry.CurrentValue
?
因此,在上面的示例中,由于枚举顺序之前的Status.Load()
调用,即使所有Status都在对象缓存中,第一次访问Order.OrderStatus
时 ,数据库查询是否也会执行?
每当访问导航属性时,EF都会首先检查对象缓存,因此,如果所有Status实体都已经加载并访问Order.OrderStatus,则不应发出数据库查询。 如果引用已经在对象缓存中,则在实现过程中将填充DbReferenceEntry。
如果您担心发出的查询数量,请考虑关闭“延迟加载”,这样,在发出自动查询的情况下,您将获得null值。
有关更多性能指南,请参见本文: http : //msdn.microsoft.com/zh-cn/data/hh949853.aspx
我没有测试以下语句(通过观察在探查器中何时实际运行哪个SQL查询),它们只是猜测:
即使在枚举顺序之前调用Status.Load(),即使所有Status都在对象高速缓存中,数据库查询也会执行吗?
如果没有执行数据库查询,我很确定原因不是 特定的 context.Status.Load()
被调用。 这只是执行context.Status.Load()
时Status
数据库表的快照。 在您枚举订单时,EF不能确定“ Status
表在此期间没有更改,并且已插入其他“ Status
行。 因此,为避免错误的数据表示,EF 必须运行新的查询-除非EF有其他方法可以识别是否需要查询。
还有其他方法,在这种情况下,因为Order
实体仅引用 Status
,而没有Status
集合。 加载Order
-通过枚举context.Orders.ToList()
-EF始终将外键加载到OrderStatus
,无论是否对Status
使用Include
。 如果您没有在模型中公开FK StatusId
作为属性,甚至会是这种情况。 在实现Order
时,将运行关系修正,并检查其主键与与Order
一起加载的FK具有相同值的Status
实体是否已存在于对象上下文中。 如果是,则Order.OrderStatus
属性将立即设置为该Status
实体-我想,EF会将导航属性标记为IsLoaded
。 作为参考,只能有一个匹配的实体,并且如果该实体已附加到上下文并已分配给导航属性,则运行查询是没有意义的。
因此,我会说:如果将正确的Status
对象附加到上下文,则不会运行查询。 通过运行context.Status.Load()
或仅加载此特定Status
,运行将加载此Status
任何其他查询,或通过手动将Status
附加到上下文( context.Status.Attach(...)
)。
如果Order.OrderStatus
是一个collection,这种行为必须改变。 现在加载Order
时,没有FK到Status
。 相反, Status
具有要Order
的FK。 如果首先通过context.Status.Load
或任何其他查询加载Status
实体,则Order-FK将与Status
对象一起加载。 如果您稍后加载了Order
关系修正程序,那么它将再次运行,这一次是相反的:EF查找上下文中是否有任何Status
对象具有FK引用已加载的Order
。 如果是,它将Status
添加到Order.OrderStatus
集合。 但是这一次-很大的猜测-它不能将导航属性( Order.OrderStatus
集合)标记为IsLoaded
因为它不能确保此Order
所有 Status
对象实际上都已预先加载,或者无法确定该Status
的新Status
同时,尚未将Order
添加到数据库中。
因此,我想,如果您访问Order.OrderStatus
集合,则会进行延迟加载,以确保也可以加载该Order
的潜在“ Status
对象”。 然后它将集合标记为IsLoaded
。 它可能不需要向集合添加任何其他Status
,但是至少必须检查该查询才能检查集合是否完整。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.