为什么EF 4.1不像linq-to-sql一样支持复杂查询?

9
我正在将我们的内部Web应用程序从Linq-To-Sql转换为基于现有数据库的EF CodeFirst。最近,我越来越烦恼于Linq-To-Sql的限制,而且在更新一个非常相互关联的数据库表后必须更新edmx,这让我足够沮丧,以至于我决定切换到EF。
然而,在使用Linq-To-Sql时,我遇到了几种情况,它比最新的Entity Framework更强大,我想知道是否有人知道原因?其中大部分似乎与转换有关。例如,以下查询在L2S中有效,但在EF中无效:
        var client = (from c in _context.Clients
                      where c.id == id
                      select ClientViewModel.ConvertFromEntity(c)).First();

在L2S中,这段代码可以正确地从数据库中检索客户并将其转换为ClientViewModel类型,但在EF中会出现异常,提示Linq to Entities不认识该方法(这是有道理的,因为我写了它)。
为了使EF正常工作,我必须将select移动到First()调用之后。
另一个例子是我的查询来检索客户列表。在查询中,我将其转换为匿名结构以便转换为JSON
        var clients = (from c in _context.Clients
                       orderby c.name ascending
                       select new
                       {
                           id = c.id,
                           name = c.name,
                           versionString = Utils.GetVersionString(c.ProdVersion),
                           versionName = c.ProdVersion.name,
                           date = c.prod_deploy_date.ToString()
                       })
                       .ToList();

不仅我的 Utils.GetVersionString() 方法会在 EF 中引发一个不支持的方法异常,c.prod_deploy_date.ToString() 也会引发同样的异常,而它只是一个简单的 DateTime。与之前一样,为了解决这个问题,我不得不在 ToList() 之后进行选择转换。


编辑: 我刚遇到的另一个情况是,EF 无法处理比较实体的 where 子句,而 L2S 对此没有任何问题。例如查询:

context.TfsWorkItemTags.Where(x => x.TfsWorkItem == TfsWorkItemEntity).ToList()

抛出异常,而我必须执行
context.TfsWorkItemTags.Where(x => x.TfsWorkItem.id == tfsWorkItemEntity.id).ToList() 
编辑2: 我想要添加另一个我发现的问题。显然,在EF Linq查询中不能使用数组,这可能比其他任何事情都更令我烦恼。例如,现在我将表示版本的实体转换为int[4]并尝试进行查询。在Linq-to-Sql中,我使用了以下查询:
return context.ReleaseVersions.Where(x => x.major_version == ver[0] && x.minor_version == ver[1]
                                          && x.build_version == ver[2] && x.revision_version == ver[3])
                              .Count() > 0;

这会导致以下异常:

The LINQ expression node type 'ArrayIndex' is not supported in LINQ to Entities.

编辑3:我发现了EF的另一个糟糕的Linq实现。下面是一个在L2S中工作但在EF 4.1中不起作用的查询:
        DateTime curDate = DateTime.Now.Date;
        var reqs = _context.TestRequests.Where(x => DateTime.Now > (curDate + x.scheduled_time.Value)).ToList();

这会抛出一个带有信息DbArithmeticExpression arguments must have a numeric common type. ArgumentException异常。


为什么似乎实体框架中Linq查询的能力比L2S差了?


我在L2S中有一些相当复杂的查询,尝试将其转换为L2E进行测试,但是生成的SQL L2E非常糟糕。因此,现在我仍然坚持使用L2S。 - Magnus
这似乎更像是抱怨假装成问题。 - cadrell0
不,这是一个真正的问题,因为我真的很想知道为什么 LINQ 的能力会在一代又一代中如此有限。 - KallDrexx
正如DevPrime在下面更详细地指出的那样,编写查询的方式很重要。在您的第一个示例中,是错误的,因为您的ConvertFromEntity方法不是SQL提供程序的一部分。在枚举查询之前,您绝对受限于SQL提供程序,这就是First()所做的事情,也是为什么您可以在调用之后执行方法的原因。如果您想要进行任何自定义操作,您几乎需要将查询枚举到对象或列表中,然后再使用EF应用它。 - tap
3个回答

15

编辑(2012年9月2日):已更新以反映.NET 4.5并添加了更多遗漏的功能。

这不是答案——因为唯一能回答你的问题的合格人员可能是ADO.NET团队的产品经理。

如果你查看旧数据集、linq-to-sql和EF的功能集,你会发现关键功能在新的API中被删除,因为新的API在短时间内开发,致力于提供新的花哨的功能。

以下是一些数据集中可用但后来的API中不可用的关键功能列表:

  • 批处理
  • 唯一键

Linq-to-Sql中可用但在EF中不受支持的功能(也许列表不完全正确,因为我很久没有使用L2S了):

  • 记录数据库活动
  • 延迟加载属性
  • 左外连接(DefaultIfEmpty)自第一个版本开始就有(EF自EFv4开始有)
  • 全局贪婪加载定义
  • AssociateWith-例如贪婪加载数据的条件
  • 自第一个版本开始的Code First
  • IMultipleResults支持返回多个结果集的存储过程(EF在.NET 4.5中有此功能,但没有设计器支持此功能)
  • 支持表值函数(EF在.NET 4.5中有此功能)
  • 还有一些其他的

现在我们可以列出EF ObjectContext API(EFv4)中可用但在DbContext API(EFv4.1)中缺失的功能:

  • 映射存储过程
  • 条件映射
  • 映射数据库函数
  • 定义查询、QueryViews、模型定义的函数
  • 除非将DbContext转换回ObjectContext,否则ESQL不可用
  • 除非将DbContext转换回ObjectContext,否则无法操纵独立关系的状态
  • 如果不将 DbContext 转换回 ObjectContext ,则无法使用 MergeOption.OverwriteChanges 和 MergeOption.PreserveChanges
  • 还有一些其他问题

    我个人对此的感觉只有巨大的悲伤。由于ADO.NET团队显然没有足够的资源重新实现它们,因此缺少核心功能并且在先前的API中存在的功能已被删除 - 在许多情况下,这使得迁移路径几乎不可能。整个情况更糟糕的是,缺少的功能或迁移障碍通常没有直接列出(我担心即使ADO.NET团队在接到报告之前也不知道它们)。

    因此,我认为DbContext API的整个想法都是管理失败。目前,ADO.NET团队必须维护两个API - DbContext 尚未成熟以代替 ObjectContext ,实际上它不能,因为它只是一个包装器,并且因此 ObjectContext 不可能消失。 EF开发可用的资源最可能减半了。

    还有更多相关问题。一旦我们离开ADO.NET团队,从Microsoft产品套件的角度来看,我们会看到如此多的不一致之处,以至于我有时甚至怀疑是否存在任何全局策略。

    简单地接受EF提供程序的工作方式与Linq-to-sql中运行的查询可能不起作用的事实。


  • 我可能会将此标记为答案,因为我猜想可能没有答案。我尝试做更多的事情,越是这样我就越沮丧,因为我发现在EF 4.1中有更多不起作用的查询。 - KallDrexx
    你仍然可以从DbContext中访问ObjectContext。它在4.1版本中并没有消失,只是被隐藏起来,以便于那些不需要或者可能不理解ObjectContext复杂性影响的人更容易使用。(参见:http://thedatafarm.com/blog/data-access/accessing-objectcontext-features-from-ef-4-1-dbcontext/) - jessehouwing
    @jesse:当我提到将DbContext转换回ObjectContext时,我在我的答案中多次提到了这种可能性,但那并不是答案的主要重点。 - Ladislav Mrnka
    对于今天看到这个答案的人,值得一提的是,大多数这些缺失的功能现在都可以在EF5.0 Code First中使用了。例如,延迟加载属性在设计层面上很容易打开。 - Chris Moschini

    7
    有点晚了,但我在寻找其他东西时找到了这篇文章,并想回答原始帖子中的基本问题,这些问题大部分归结为“LINQ to SQL允许表达式[x],但EF不允许”。
    答案是查询提供程序(将您的LINQ表达式树转换为实际执行并返回可枚举的一组内容的代码)在L2S和EF之间根本不同。要理解原因,您必须意识到L2S和EF之间的另一个基本差异是L2S基于表格,而EF基于实体模型。换句话说,EF使用概念实体模型,并知道底层物理模型(DB表)不一定反映概念实体。这是因为表格已规范化/去规范化,并且具有处理实体类型通用化(继承)的奇怪方式。因此,在EF中,您拥有概念模型的图片(这是您在VB/C#等中编写的对象),以及组成概念实体的物理底层表格的映射。L2S不会这样做。 L2S中的每个“模型实体”都严格是单个表格,正好是表格字段。
    到目前为止,这本身并不能真正解释原始帖子中的问题,但希望您可以开始意识到,从根本上讲,EF不是L2S +或L2S v4.0。它是一种非常不同的产品(真正的ORM),尽管在两者都使用LINQ来获取数据库数据的事实上存在一些巧合重叠。
    另一个有趣的区别是EF从头开始构建为DB-agnostic,而L2S仅适用于MS SQL Server(尽管深入研究过L2S代码的任何人都会看到有一些基础知识允许不同的DB,但最终,它仅与MS SQL Server连接)。这种差异在为什么某些表达式适用于L2S LINQ但不适用于EF LINQ方面也发挥了重要作用。 EF的查询提供程序处理规范化DB表达式,换句话说,几乎所有关系数据库中都具有SQL查询等效项的LINQ表达式。底层EF引擎(查询提供程序)将LINQ表达式转换为这些规范化DB表达式,然后将规范化表达式树移交给特定的DB提供程序(例如Oracle或MySQL的EF提供程序),在那里将其转换为特定产品的SQL。您可以在此处查看这些规范化表达式应如何由各个特定产品的提供程序进行翻译:http://msdn.microsoft.com/en-us/library/ee789836.aspx 此外,EF通过扩展允许一些特定于产品的DB函数(存储函数)作为表达式。底层的特定于产品的提供程序负责提供和翻译这些内容。
    鉴于此,EF仅允许是DB规范化表达式或存储特定功能的表达式,因为树中的所有表达式都将转换为SQL以针对DB执行。
    与L2S的区别在于,L2S将它所能处理的任何表达式传递给数据库,然后对返回的物化对象集上不能转换为SQL的任何表达式进行执行。虽然使用L2S看起来更简单,但你没有看到的是,有一半的表达式实际上并没有以SQL的形式传递到数据库,这可能会导致一些非常低效的查询,带回较大的数据集,然后再使用常规对象LINQ反对它们进行迭代,用于L2S无法转换为SQL的其他表达式。
    您可以通过使用EF将物化数据返回到内存中的对象集,并在该集合中使用其他的LINQ语句来获得完全相同的效果 - 就像L2S一样,但在这种情况下,您只需显式地执行它,就像当您说在使用非DB规范表达式之前必须调用.First()一样。同样,您可以在使用无法转换为SQL的附加表达式之前调用.ToArray()或.ToList()。
    另一个重要的区别是,在EF中,实体必须整体使用。真正的模型实体表示被整体事务处理的概念对象。例如,你永远不会只有一半的用户。用户是一个状态依赖于所有字段的对象。如果要返回部分实体或多个实体的扁平联接,则必须定义一个投影(EF称为复杂类型),或者可以使用一些新的4.1 / 4.2 / 4.3 POCO功能。

    1
    现在Entity Framework是开源的,从团队问题的评论中可以很容易地看出,其中一个目标是将EF作为多个数据库顶部的开放层提供。公平地说,微软显然只在他们的SQL Server上实现了该层,但还有其他实现,比如DevArt的MySql EF Connector
    作为这个目标的一部分,保持公共接口有点受限是明智的,并且尝试添加一个额外的层来询问 - 好吧,这些内容中的一些可能会在内存中完成,这些内容中的一些可能会在SQL中完成,谁知道 - 绝对会使其他实现者试图将EF与这个或那个数据库联系起来变得更加复杂。
    所以,我同意这里的其他答案-你必须问问团队-但是您也可以在公共错误跟踪器和他们的其他出版物中获得有关该团队方向的大量信息,这似乎是一个明确的动机。
    LINQ to SQL和EF的主要区别在于EF在必须在内存中运行代码时会抛出异常,如果您是Expressions专家,则可以继续封装DbContext类,使其像LINQ to SQL一样工作。另一方面,您获得的东西是一袋子杂物 - 您使生成SQL变得隐式而不是显式,并且当它触发时,这可能被视为性能和控制的损失,以换取灵活性/撰写的便利性。

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