LINQ To Entities和延迟加载

9
今天在一篇有争议的博客文章中,Hackification发表了对新LINQ To Entities框架中似乎存在的错误的看法:

Suppose I search for a customer:

var alice = data.Customers.First( c => c.Name == "Alice" );

Fine, that works nicely. Now let’s see if I can find one of her orders:

 var order = ( from o in alice.Orders
          where o.Item == "Item_Name"
          select o ).FirstOrDefault();

LINQ-to-SQL will find the child row. LINQ-to-Entities will silently return nothing.

Now let’s suppose I iterate through all orders in the database:

foreach( var order in data.Orders ) { 
Console.WriteLine( "Order: " + order.Item ); }

And now repeat my search:

var order = ( from o in alice.Orders
          where o.Item == "Item_Name"
          select o ).FirstOrDefault();

Wow! LINQ-to-Entities is suddenly telling me the child object exists, despite telling me earlier that it didn’t!

我的初步反应是这一定是个bug,但经过进一步考虑(并得到ADO.NET团队的支持),我意识到这种行为是由实体框架在从数据上下文中获取Alice时未进行订单子查询的惰性加载导致的。
这是因为订单是一个LINQ-To-Object查询:
var order = ( from o in alice.Orders
      where o.Item == "Item_Name"
      select o ).FirstOrDefault();

他在foreach循环中没有以任何方式访问数据上下文:

 foreach( var order in data.Orders )

正在访问数据上下文。

LINQ-To-SQL实际上为Orders创建了延迟加载属性,因此在访问时会执行另一个查询,而LINQ to Entities则由您手动检索相关数据。

现在,我不是ORM的忠实粉丝,这正是原因。我发现为了让你想要的所有数据随时准备就绪,它们会在你背后反复执行查询,例如,上面的linq-to-sql查询可能每行客户都会运行一个额外的查询来获取订单。

然而,EF不这样做似乎严重违反了最小惊奇原则。虽然这是一种技术上正确的做法(您应该运行第二个查询以检索订单,或者从视图中检索所有内容),但它并不像ORM那样表现出人们的预期。

那么,这是好的框架设计吗?还是微软正在为我们过度思考?

6个回答

12

Jon,

我也一直在使用Linq到实体。相对于Linq到SQL,它还有很长的路要走。我不得不使用Linq到实体来处理表继承类型的问题。最近我找到了一篇不错的文章,讲解了一个公司使用两种不同的ORM技术的整个过程here

但是你可以通过这样做来进行延迟加载:

// Lazy Load Orders 
var alice2 = data.Customers.First(c => c.Name == "Alice");

// Should Load the Orders
if (!alice2.Orders.IsLoaded)
    alice2.Orders.Load();

或者你可以在原始查询中包含订单:

// Include Orders in original query
var alice = data.Customers.Include("Orders").First(c => c.Name == "Alice");

// Should already be loaded
if (!alice.Orders.IsLoaded)
    alice.Orders.Load();

希望这有所帮助。

Dave


2
我同意,这篇博客文章并不是完全了解情况的。我认为他们应该花更多时间学习L2E,而不是在批评它之前就下定论,因为他们不知道必须要Load(),所以引起了巨大的骚动。 - naspinski

5

那么,这是好的框架设计吗?还是微软替我们想得过多了?

好吧,让我们来分析一下 - 微软为我们做出的所有思考实际上让我们变得更懒。但总的来说,这确实使我们更有生产力(在大多数情况下)。所以他们是在过度思考还是仅仅帮我们思考?


2
失去了几天时间来解决这个问题,我深表同情。
如果有“错误”的话,那就是合理地期望抽象层将隔离这些问题。从LINQ到实体再到数据库层,更加如此。
例如,必须从使用LingToSQL的MS-SQL切换到使用LinqToEntities的MySQL,则可以假定至少LINQ应该相同,而不仅仅是为了节省重新编写程序逻辑的成本。
由于持久性机制在底层发生了变化而不得不向代码中添加.Load()和/或LINQ中的.Include()似乎有些令人不安,尤其是因为静默故障。LINQ层至少应该表现一致。
许多ORM框架使用代理对象来动态透明地加载延迟对象,而不只是返回null,虽然我会满足于集合未加载异常。
我倾向于不相信他们是为了你的利益而故意这样做的借口;其他ORM框架让您注释需要饥饿或惰性加载。这里也可以这样做。

2
如果LINQ-to-Sql和LINQ-to-Entities来自两个不同的公司,这是可以接受的差异 - 没有法律规定所有的LINQ-To-Whatevers都必须以相同的方式实现。
然而,它们都来自微软 - 我们不应该需要对他们的内部开发团队和流程有深入了解才能知道如何使用两个看起来完全相同的东西。
ORMs有其存在的意义,并确实填补了人们在尝试完成任务时的空缺,但ORM使用者必须确切地知道他们的ORM是如何完成任务的 - 把它当作一个不可渗透的黑盒子只会给你带来麻烦。

1

虽然你不应该了解微软内部的开发团队和流程,但事实是这两种技术是完全不同的。

LINQ to SQL 的设计决策是为了简单起见,隐式地延迟加载集合。ADO.NET 实体框架团队不想在用户不知情的情况下执行查询,因此他们设计了 API 以便在第一个版本中显式加载。

LINQ to SQL 已经移交给 ADO.NET 团队,因此您可能会在未来看到 API 的整合,或者将 LINQ to SQL 折叠到实体框架中,或者您可能会看到 LINQ to SQL 因忽视而逐渐被弃用。


1

我对ORM不是很了解,但作为LinqToSql和LinqToEntities的用户,我希望当你尝试查询Alice的订单时,在进行Linq查询时它会为你执行额外的查询(而不是什么也不查询或者为每一行查询所有内容)。

这似乎是一个自然的期望。

from o in alice.Orders where o.Item == "Item_Name" select o

工作是人们首先使用ORM的原因之一(简化数据访问),因此很重要。

我越读关于LinqToEntities的文章,我越认为LinqToSql可以满足大多数开发者的需求。我通常只需要表的一对一映射。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接