为什么EF 4.1不支持复杂查询以及linq-to-sql?

时间:2011-06-20 15:57:47

标签: entity-framework linq-to-sql entity-framework-4.1

我正在将内部Web应用程序从Linq-To-Sql转换为现有数据库中的EF CodeFirst。我最近越来越多地对Linq-To-Sql的限制感到恼火,并且在更新了一个交织在一起的数据库表后不得不更新edmx,这最终让我感到很沮丧,无法切换到EF。

但是,我遇到了几种情况,使用linq和Linq-To-Sql比最新的实体框架更强大,我想知道是否有人知道它的原因?其中大部分似乎都涉及转型。例如,以下查询适用于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无法处理哪些子句比较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();

这会抛出ArgumentException消息DbArithmeticExpression arguments must have a numeric common type.

<小时/> 为什么看起来他们降低了EF中Linq查询的能力而不是L2S?

3 个答案:

答案 0 :(得分:15)

编辑(2012年9月2日):已更新以反映.NET 4.5并添加了一些缺少的功能

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

如果检查旧数据集的功能集,然后检查linq-to-sql和EF,则会发现新API中删除了关键功能,因为较新的API是在更短的时间内开发的,需要付出巨大努力才能提供新的功能。

仅列出DataSet中可用但在以后的API中不可用的一些关键功能:

  • 批处理
  • 唯一键

Linq-to-Sql中可用的功能但EF不支持(可能列表不完全正确,我很长时间没有使用过L2S):

  • 记录数据库活动
  • 延迟加载的属性
  • 自第一个版本以来的左外连接(DefaultIfEmpty)(自EFv4以来EF具有它)
  • 全球热切加载定义
  • AssociateWith - 例如急切加载数据的条件
  • 自第一个版本以来的第一个代码
  • IMultipleResults支持返回多个结果集的存储过程(EF在.NET 4.5中有它,但没有设计器支持此功能)
  • 支持表值函数(EF在.NET 4.5中具有此功能)
  • 还有其他人

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

  • 映射存储过程
  • 条件映射
  • 映射数据库功能
  • 定义查询,QueryViews,模型定义函数
  • 除非您将DbContext转换回ObjectContext
  • ,否则ESQL不可用
  • 除非您将DbContext转换回ObjectContext
  • ,否则无法操纵独立关系状态
  • 除非您将MergeOption.OverwriteChanges转换回MergeOption.PreserveChanges
  • ,否则无法使用DbContextObjectContext
  • 还有其他人

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

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

还有更多相关问题。一旦我们离开ADO.NET团队并从MS产品套件的角度看待问题,我们会看到很多差异,我有时甚至怀疑是否有任何全球战略。

简单地说EF的提供者以不同的方式工作这一事实,并且在Linq-to-sql中工作的查询不必与EF一起工作。

答案 1 :(得分: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无关,而L2S仅适用于MS SQL Server(尽管任何嗅到L2S代码足够深的人都会看到有一些基础允许不同的数据库,但最后,它只与MS SQL Server绑定)。这种差异对于为什么某些表达式在L2S LINQ中起作用而在EF LINQ中起作用也起着重要作用。 EF的查询提供程序处理规范的DB表达式,在简单的英语中表示在几乎所有关系数据库中都具有SQL查询等价物的LINQ表达式。底层EF引擎(查询提供程序)将LINQ表达式转换为这些规范的DB表达式,然后将规范表达式树交给特定的数据库提供程序(比如Oracle或MySQL的EF提供程序),并将其转换为特定于产品的SQL。您可以在此处看到这些规范表达式应如何由特定于产品的提供商翻译:http://msdn.microsoft.com/en-us/library/ee789836.aspx

此外,EF允许一些特定于产品的数据库函数(存储函数)作为表达式通过扩展。基础产品特定提供商负责提供和翻译这些。

在这种情况下,EF只允许表达式是DB规范表达式或特定于商店的函数,因为树中的所有表达式都将转换为SQL,以便对DB执行。

与L2S的不同之处在于,L2S将其可以从其有限的SQL生成器传递给DB的任何表达式,然后执行它无法转换为返回的具体化对象集上的SQL的任何表达式。虽然这使得使用L2S看起来更简单,但你没有看到的是,你的表达式的一半实际上并没有像SQL那样进入数据库,这会导致一些非常低效的查询带回更大的数据集然后被迭代再次在CLR内存中使用常规对象LINQ对其他表达式执行操作,而L2S无法转换为SQL。

通过使用EF将具体化数据返回到内存中的对象集,然后在内存中使用该集合上的其他LINQ语句,就可以获得与EF完全相同的效果 - 就像L2S一样,但在这种情况下,你只需要明确地做,就像你说你必须在使用非DB规范表达式之前调用.First()。同样,在使用无法转换为SQL的其他表达式之前,可以调用.ToArray()或.ToList()。

另一个很大的区别是,在EF中,实体必须全部使用。真实模型实体表示整体交易的概念对象。例如,你永远不会有半个用户。用户是一个状态取决于所有字段的对象。如果要返回部分实体或多个实体的展平连接,则必须定义投影(EF称为复杂类型),或者您可以使用一些新的4.1 / 4.2 / 4.3 POCO功能。

答案 2 :(得分:1)

现在Entity Framework is open source,很容易看到,特别是从团队问题的评论中,其中一个目标是在多个数据库之上提供EF作为开放层。公平地说,微软毫不奇怪只在SQL Server之上实现了该层,但还有其他实现,如DevArt's MySql EF Connector

作为该目标的一部分,明智的做法是保持公共接口有限,并尝试添加一个要求的附加层 - 好吧,其中一些可能在内存中完成,其中一些可能在SQL中完成,谁知道 - 对于试图将EF绑定到这个或那个数据库的其他实现者来说,肯定会使工作变得复杂。

所以,我同意这里的另一个答案 - 你必须要问团队 - 但是你也可以在公共bug跟踪器和他们的其他出版物上获得关于该团队方向的大量信息,这看起来像是一个明确的动机。

那说LINQ to SQL和EF之间的主要区别在于EF在必须在内存中运行的代码上引发异常的方式,如果你是一个Expressions忍者,没有什么能阻止你进行下一步的包装DbContext类使其工作就像LINQ to SQL一样。另一方面,你获得的是一个混合包 - 你在生成SQL时,它是隐式的而不是显式的,当它激发时,这可以被视为性能和控制的损失以换取灵活性/易用性创作。