C# “为什么?”;IQueryable.Where()”中;提供不同于“的结果”;IEnumerable.Where();在这个查询中?
我试图使用linq从EF Core数据集中筛选一些行,但我不明白为什么筛选IEnumerable和筛选IQueryable会得到不同的结果C# “为什么?”;IQueryable.Where()”中;提供不同于“的结果”;IEnumerable.Where();在这个查询中?,c#,entity-framework,linq,entity-framework-core,C#,Entity Framework,Linq,Entity Framework Core,我试图使用linq从EF Core数据集中筛选一些行,但我不明白为什么筛选IEnumerable和筛选IQueryable会得到不同的结果 var query = _db.Jobs .IsDelivered() .Include(a => a.JobExtras) .Include(a => a.Tips) .Include(a => a.Payments)
var query = _db.Jobs
.IsDelivered()
.Include(a => a.JobExtras)
.Include(a => a.Tips)
.Include(a => a.Payments)
.HasPayments();
var query1 = query
.ToList()
.Where(a => a.Payments.Sum(b => b.Amount)
- a.Price.Value
- a.Discount
- a.JobExtras.Sum(c => c.Price)
- a.Tips.Sum(d => d.Amount)
> 0);
var query2 = query
.Where(a => a.Payments.Sum(b => b.Amount)
- a.Price.Value
- a.Discount
- a.JobExtras.Sum(c => c.Price)
- a.Tips.Sum(d => d.Amount)
> 0);
Debug.WriteLine($"Record Count[Before Where Clause]: {query.Count()}");
Debug.WriteLine($"Record Count[ToList() version]: {query1.Count()}");
Debug.WriteLine($"Record Count[w/out ToList()]: {query2.Count()}");
以下是输出:
Record Count[Before Where Clause]: 8379
Record Count[ToList() version]: 5921
Record Count[w/out ToList()]: 0
为什么IEnumerable版本生成5921条记录,而IQueryable版本生成0条记录?第一个查询在内存中使用.NET类型和
Enumerable.Sum()执行。跳过传递到Sum
中的任何空值,因此您的一些数据正在通过>0
测试
第二个查询在数据库中执行,并使用SQL的SUM
。如果Where
子句中表达式的任何部分包含null,则该表达式的计算结果将为null。获取该表达式并对其进行比较(null>0
)在SQL中的计算结果始终为false,因此所有行都将被筛选
您的第二个查询可能是可修复的。我没有证据证明这一点,因为我不知道您的模型(即,什么是可空的或不可空的),也不确定EF是否可以转换这一点:
.Where(a => a.Payments.Where(x => x != null).Sum(b => b.Amount)
- a.Price.Value
- a.Discount
- a.JobExtras.Where(x => x != null).Sum(c => c.Price)
- a.Tips.Where(x => x != null).Sum(d => d.Amount)
> 0);
因此,根据Kit和madreflection提供的信息:
我刚刚为DB重新设定了种子,并确保在3个子表中有行:Payments、JobExtras和Tips
瞧,这个查询既适用于IQueryable版本,也适用于IEnumerable版本。我得到的行数相同
因此,总而言之,问题的答案是IQueryable查询的问题是,如果数据库中没有我试图求和的对象的子行,SQL将返回NULL而不是0
谢谢你的帮助
这种行为很有趣
那么现在,有没有办法修复IQueryable查询以应对子对象(小费、付款、临时工)可能是“空集”(无行)的可能性
=============
工作解决方案(归功于@Steve Py&@madreflection)
好吧,我有几件事要尝试,但我读到你发现问题在于失踪的孩子
我做了一个快速测试,以下内容在您的情况下应该有效:
var query2 = query
.Where(a => a.Payments.Sum(b => b.Amount)
- a.Price.Value
- a.Discount
- (a.JobExtras.Any() ? a.JobExtras.Sum(c => c.Price) : 0)
- (a.Tips.Any() ? a.Tips.Sum(d => d.Amount) : 0)
> 0);
将Jobs.Price视为查询中唯一可为空的值,则Iqueryable linq查询可以重写如下:
.Where(a => a.Payments.Sum(b => b.Amount)
- a.Price.Value
- a.Discount
- a.JobExtras.Sum(c => c.Price ?? 0)
- a.Tips.Sum(d => d.Amount)
> 0);
您的计算中是否有NULL
s列?NULL
上的任何数学或逻辑都将产生NULL
,包括NULL>0
->NULL
,这不是真的,因此在功能上等同于假。我想可能也是这样,所以我删除了唯一一个可以为NULL的术语“Price”,但结果相同。另外,如果是这样的话,这不应该像query2那样影响query1吗?不。当您调用ToList
,它会执行查询,此时您正在对.NET类型/值进行操作Sum
将忽略null
而不是产生null
,Count
将对null
项进行计数而不是忽略它。如果您的源数据没有NULL
s,这将是毫无意义的,但我认为您可能会在没有可聚合项的情况下得到NULL
。谢谢您的思路。madreflection正确地提到NULL是问题,但不是正确的领域。我认为问题不是一个可为空的字段,而是一个可为空的集合。Iqueryable是SQL,SQL处理的是“集合”,而不是对象。因此,当您尝试执行Tips.Sum(诸如此类)时,如果没有Tips行(有几个作业对象没有“子”tip行),SQL将返回“NULL”,其中as.NET返回一个“0”,使得整个查询无法通过SQL使用。想法?如果我上次的评论是正确的,这意味着您尝试的“修复”也不会起作用,因为Tips。如果DB中有0个Tips行,其中(x=>x!=null)仍将导致null。我将此标记为正确答案,因为它让我找到解决方案。我在下面创建了一个带有最终工作解决方案的答案。谢谢大家的帮助。@FrankBusalacchi:FWIW,我在第二条评论中提到了集合,当时我说“没有要聚合的项目”。@madreflection——是的,我同意你也提到了,你也是第一个回应的人!这就是为什么我在回答中说,根据你们双方提供的信息。。这也是我对你的评论投赞成票的原因。我感谢你在我的问题上的帮助和努力!感谢您,也感谢所有花时间阅读本文并提供想法、建议和解决方案的人。所有对象中唯一可为空的字段是Jobs.Price。尝试做b=>Amount??0甚至不会编译,因为Amount不可为null。不知何故,当数据库中没有Payment/Tips/jobetras行时,表达式a.jobetras.Sum(c=>c.Price)必须计算为0而不是null。您可以a.Payments.Sum(b=>(十进制?)b.Amount吗??0M)
相反,正如Kit所提到的,当IQueryable.Sum()呈现一个空集合时,它的行为就是IQueryable.Sum()的行为。当集合为空时,IQueryable.Sum()=NULL。当集合为空时,IEnumerable.Sum()=0。SQL纯粹主义者可能会认为这是正确的,但在我看来,IQueryable.Sum()和IEnumerable.Sum应该具有一致的行为,要么都返回Null,要么都返回0;这一切都有道理。我只是想简化你的表达。Sum
的结果并不总是0
。对于可为null的值类型,它是null
,您可以将其合并到0
。将b.Amount
(例如)强制转换为null将消除对Any
的调用,从而消除三元运算符的需要。从理论上讲,您必须测试它以确认它能够生成更好的SQL。@madreflection:我刚刚交换了您的建议,它也确实起了作用。我当然更喜欢三元结构,虽然我还没有研究过,但我同意y
.Where(a => a.Payments.Sum(b => b.Amount)
- a.Price.Value
- a.Discount
- a.JobExtras.Sum(c => c.Price ?? 0)
- a.Tips.Sum(d => d.Amount)
> 0);