All*_*nek 9 .net c# entity-framework entity-framework-4.1 dbcontext
我正在使用EF 4.3.1 ...刚刚升级到4.4(问题仍然存在),由EF 4.x DbContext Generator生成的数据库优先POCO实体.我有以下数据库名为'Wiki'(用于创建表和数据的SQL脚本在这里):

编辑Wiki文章时,不会更新其记录,而是将新版本作为新记录插入,并使修订计数器递增.在我的数据库中有一位作者"John Doe",它有两篇文章,"文章A"和"文章B",其中文章A有两个版本(1和2),但文章B只有一个版本.

我禁用了延迟加载和代理创建(这里是我使用LINQPad的示例解决方案).我想获得由名字以"John"开头的人创建的文章的最新版本,所以我做了以下查询:
Authors.Where(au => au.Name.StartsWith("John"))
.Select(au => au.Articles.GroupBy(ar => ar.Title)
.Select(g => g.OrderByDescending(ar => ar.Revision)
.FirstOrDefault()))
Run Code Online (Sandbox Code Playgroud)
这会产生错误的结果,并仅检索第一篇文章:

结交查询小的变化,通过更换.FirstOrDefault()与.Take(1)下面的查询结果:
Authors.Where(au => au.Name.StartsWith("John"))
.Select(au => au.Articles.GroupBy(ar => ar.Title)
.Select(g => g.OrderByDescending(ar => ar.Revision)
.Take(1)))
Run Code Online (Sandbox Code Playgroud)
令人惊讶的是,此查询产生了正确的结果(尽管有更多嵌套):

我假设EF生成略有不同的SQL查询,一个只返回单个文章的最新版本,另一个返回所有文章的最新版本.两个查询生成的丑陋SQL差别不大(比较:针对.FirstOrDefault()的SQL与针对.Take (1)的SQL相比,但它们都返回正确的结果:
.FirstOrDefault()

.Take(1) (重新排列列顺序以便于比较)

因此,罪魁祸首不是生成的SQL,而是EF对结果的解释.为什么EF将第一个结果解释为单个Article实例,而将第二个结果解释为两个Article实例?为什么第一个查询返回不正确的结果?
编辑:我已经打开了关于Connect 的错误报告.如果您认为解决此问题很重要,请进行投票.
查看:
http://msdn.microsoft.com/en-us/library/system.linq.enumerable.firstordefault
http://msdn.microsoft.com/en-us/library/bb503062.aspx
有非常好的解释Take 是如何工作的(惰性的,早期的 brekaing),但没有 FirstOrDefault ..更重要的是,看到 Take 的解释,我“猜测”Take 的查询可能会由于尝试模拟惰性而减少行数SQL中的评估,你的情况表明它是另一种方式!我不明白你为什么会观察到这种效应。
它可能只是特定于实现的..对我来说, Take(1) 和 FirstOrDefault 可能看起来像TOP 1,但是从功能的角度来看,它们的“惰性”可能略有不同:一个函数可以评估所有元素并首先返回,第二个可能首先评估然后返回它并中断评估。这只是可能发生的情况的“提示”。对我来说,这是无稽之谈,因为我没有看到关于这个主题的文档,而且一般来说,我确信 Take/FirstOrDefault 都是惰性的,应该只评估前 N 个元素。
在查询的第一部分中, group.Select+orderBy+TOP1 是一个“明确的指示”,表明您对每组列中具有最高“值”的单行感兴趣 - 但事实上,没有简单的方法在 SQL 中声明这一点,因此对于 SQL 引擎和 EF 引擎来说,指示根本不那么清晰。
对于我来说,您呈现的行为可能表明 FirstOrDefault 被 EF 翻译器向上“传播”了一层内部查询太多,就好像到了 Articles.GroupBy() (您确定您没有在OrderBy?:) ) - 这将是一个错误。
但 -
由于差异必定存在于含义和/或执行顺序中的某个位置,让我们看看 EF 可以猜测您的查询的含义。作者实体如何获取其文章?EF 如何知道哪一篇文章要绑定到您的作者?当然是nav属性。但为什么只有部分文章被预加载呢?看起来很简单 - 查询返回一些带有列的结果,列描述了整个作者和整个文章,因此让我们将它们映射到作者和文章,并让它们通过导航键相互匹配。好的。但添加复杂的过滤......?
使用像按日期这样的简单过滤器,它是所有文章的单个子查询,行按日期截断,并且所有行都被消耗。但是,编写一个使用多个中间排序并生成多个文章子集的复杂查询怎么样?哪个子集应该绑定到结果作者?他们所有人的联盟?这将使所有顶级 where-like 子句失效。第一个?废话,第一个子查询往往是中介助手。因此,当一个查询被视为一组具有相似结构的子查询时,所有子查询都可以作为导航属性部分加载的数据源,那么很可能只有最后一个子查询被视为实际结果。这都是抽象思维,但它让我注意到 Take() 与 FirstOrDefault 以及它们的整体 Join 与 LeftJoin 含义实际上可以改变结果集扫描的顺序,并且不知何故,Take() 以某种方式优化并在一次扫描中完成整个结果,从而一次访问所有作者的文章,并且 FirstOrDefault 被执行为直接扫描for each author * for each title-group * select top one and check count and substitue for null,多次生成每个作者的小型单项文章集合,从而产生一个结果 - 仅来自最后一个标题 -分组访问。
这是我能想到的唯一解释,除了明显的“BUG!” 喊。作为 LINQ 用户,对我来说,这仍然是一个错误。要么根本不应该发生这样的优化,要么它也应该包括 FirstOrDef - 因为它与 Take(1).DefaultIfEmpty() 相同。呵呵,顺便问一下 - 你尝试过吗?正如我所说,由于 JOIN/LEFTJOIN 的含义,Take(1) 与 FirstOrDefault 不同 - 但 Take(1).DefaultIfEmpty() 实际上在语义上是相同的。看看它在 SQL 中生成什么 SQL 查询以及在 EF 层中产生什么结果可能会很有趣。
我必须承认,部分加载中相关实体的选择对我来说从来都不清楚,而且我实际上已经很长时间没有使用部分加载了,因为我总是陈述查询,以便明确定义结果和分组(*).. 因此,我可能只是忘记了其内部工作的一些关键方面/规则/定义,也许,即。它实际上是从结果集中选择每个相关记录(不仅仅是我现在描述的最后一个子集合)。如果我忘记了什么,那么我刚才描述的一切显然都是错误的。
(*) 在你的情况下,我也会将 Article.AuthorID 设为导航属性(设置公共作者作者),然后重写查询,使其更加扁平/流水线化,例如:
var aths = db.Articles
.GroupBy(ar => new {ar.Author, ar.Title})
.Take(10)
.Select(grp => new {grp.Key.Author, Arts = grp.OrderByDescending(ar => ar.Revision).Take(1)} )
Run Code Online (Sandbox Code Playgroud)
然后分别用成对的作者和艺术填充视图,而不是尝试部分填充作者并仅使用作者。顺便提一句。我还没有针对 EF 和 SServer 进行测试,它只是在 JOIN 情况下“翻转查询”和“展平”子查询的示例,并且对于 LEFTJOIN 不可用,因此如果您还想查看没有文章的作者,它必须从作者开始,就像你原来的查询一样。
我希望这些松散的想法能帮助你找到“为什么”。