OrderBy().FirstOrDefault(<condition>) 与 Where(<condition>).OrderBy().FirstOrDefault() 的区别

8
我正在使用EntityFramework 6.1.3和SQL Server在一个C#项目中。 我有两个查询,它们基本上应该做相同的事情。
1.
Exams.GroupBy(x=>x.SubjectID).Select(x => x.OrderBy(y => y.Level.NumericValue).FirstOrDefault(y => y.Date.Value > DateTime.Today))

2.
Exams.GroupBy(x=>x.SubjectID).Select(x => x.Where(y => y.Date.Value > DateTime.Today).OrderBy(y => y.Level.NumericValue).FirstOrDefault())

然而,第一个查询的结果与我去掉order by时的结果相同。第二个查询按预期工作。
当我通过Linqpad查看生成的SQL时,第一个查询中没有order by子句。
我的问题是,为什么第一个查询不像第二个查询那样工作? 我一直以为...
.Where(<condition>).FirstOrDefault() == .FirstOrDefault(<condition>)

这段文字的意思是:在这里已经有答案了:.Where(<condition>).FirstOrDefault() vs .FirstOrDefault(<condition>)
编辑:我尝试了一下,发现这两个查询产生相同的SQL输出。
Exams.GroupBy(x => x.SubjectID).Select(x => x.FirstOrDefault(y => y.Date.Value > DateTime.Today))
Exams.GroupBy(x => x.SubjectID).Select(x => x.OrderBy(y => y.Level.NumericValue).FirstOrDefault(y => y.Date.Value > DateTime.Today))

尽管它看起来像个虫子,但我仍然不完全相信。

也许你应该回应一下已经发布的答案。我认为他们已经解释了这个问题。 - usr
5个回答

3
尽管在stackoverflow上的相关bug报告被关闭,但这明显是一个bug。我用类似的OrderByDescendingFirstOrDefault结合谓词的方式,在GroupBy中遇到了完全相同的问题。

看起来Entity Framework不支持带有谓词的FirstOrDefault语句,至少在这个问题的背景下不支持。正如问题所述,以下表达式使用ORDER BY子句生成正确的SQL查询:

Exams
    .GroupBy(x => x.SubjectID)
    .Select(x => x
        .Where(y => y.Date.Value > DateTime.Today)
        .OrderBy(y => y.Level.NumericValue)
        .FirstOrDefault())

然而,在FirstOrDefault语句中添加任何谓词都会让EF困惑,并导致没有ORDER BY的查询结果:
Exams
    .GroupBy(x => x.SubjectID)
    .Select(x => x
        .Where(y => y.Date.Value > DateTime.Today)
        .OrderBy(y => y.Level.NumericValue)
        .FirstOrDefault(y => true)) // Tautology shouldn't have any effect, but it does!

唯一的解决方法是将 FirstOrDefault(predicate) 语句拆分为 Where(predicate).FirstOrDefault()。请确保在代码中添加注释以解释这个决定,因为 ReSharper 建议使用单个 FirstOrDefault(predicate) 语句。但这样做会导致查询出错!

1

要解决此问题,请调用ToList()方法。


1
这个问题的答案取决于LINQ提供程序,例如EF。一个LINQ提供程序支持的特定模式很少被完全记录。我不知道任何关于这个问题的EF文档。不清楚这是否是一个错误或者它是否应该起作用。
LINQ技术本身不能保证这总是有效。
我认为这应该起作用,或者至少高度期望它可以。创建一个简短的Github问题指向这个问题。团队非常敏感。
目前你别无选择,只能选择可行的模式。

1

我不确定这是否是问题的根本原因,但是这两个查询之间存在微妙的差异——OrderBy和Where的相对位置。在关系世界中,筛选器使子查询中进行的排序无效。EF尝试通过在筛选器后“提升”一些OrderBys来弥补这种差异,但可能无法正确地提升此模式。请尝试在第二个查询中交换OrderBy和Where的位置:

Exams.GroupBy(x => x.SubjectID).Select(x => x.OrderBy(y => y.Level.NumericValue).Where(y => y.Date.Value > DateTime.Today).FirstOrDefault())

请尝试查看修改后的SQL是否与未更改版本相同。如果相同,则EF中肯定存在错误(我希望生成的SQL与查询1相同,但与原始查询2不同)。否则,这可能只是order by lifting算法的限制。

一般来说,如果您想让EF更容易处理,请在Where之后放置OrderBy。


-1

.Where(<condition>).FirstOrDefault() 将从您的条件返回一个IEnumerable列表,然后获取该列表的第一个或默认值。而第二个.FirstOrDefault(<condition>) 将应用条件并获取符合条件的第一个值。因此这两个会有不同的执行结果


1
所有的查询都保留在IQueryable领域中。IEnumerable在这里不是一个问题。 - usr
好的,我没有去查阅资料,只是凭空说了一些话。IEnumerable https://msdn.microsoft.com/en-us/library/vstudio/bb340482(v=vs.100).aspx 确实有一个FirstOrDefault的实现,但是我的回答在Where方面还有所欠缺。 - Hano Johannes Rossouw

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