在今天的一篇有争议的博客文章中,Hackification 对新的 LINQ To Entities 框架中似乎存在的错误发表了看法:
假设我搜索客户:
var alice = data.Customers.First( c => c.Name == "Alice" );
很好,效果很好。现在让我们看看我是否能找到她的订单之一:
var order = ( from o in alice.Orders where o.Item == "Item_Name" select o ).FirstOrDefault();
LINQ-to-SQL 将找到子行。LINQ-to-Entities 将不返回任何内容。
现在假设我遍历数据库中的所有订单:
foreach( var order in data.Orders ) { Console.WriteLine( "Order: " + order.Item ); }
现在重复我的搜索:
var order = ( from o in alice.Orders where o.Item == "Item_Name" select o ).FirstOrDefault();
哇!LINQ-to-Entities 突然告诉我子对象存在,尽管之前告诉我它不存在!
我最初的反应是这一定是一个错误,但经过进一步考虑(并得到 ADO.NET 团队的支持),我意识到这种行为是由实体框架引起的,而不是在 Alice 被拉出时延迟加载 Orders 子查询数据上下文。
这是因为 order 是一个 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 所期望的那样。
那么,这是好的框架设计吗?还是微软在为我们考虑这个问题?