Sum()在Entity Framework查询中返回null

14

我有一个包含以下代码行的大型实体框架查询。

var programs = from p in Repository.Query<Program>()
               where p.OfficeId == CurrentOffice.Id
               let totalCharges = p.ProgramBillings.Where(b => b.Amount > 0 && b.DeletedDate == null).Select(b => b.Amount).Sum()
               let totalCredits = p.ProgramBillings.Where(b => b.Amount < 0 && b.DeletedDate == null).Select(b => -b.Amount).Sum()
               let billingBalance = (totalCharges - totalCredits)

当我将数据具体化时,出现以下错误:

类型转换为“Decimal”值类型失败,因为被具体化的值为空。结果类型的泛型参数或查询必须使用可空类型。

如果我按如下方式更改查询(添加两个类型转换),则错误消息消失了。

var programs = from p in Repository.Query<Program>()
               where p.OfficeId == CurrentOffice.Id
               let totalCharges = (decimal?)p.ProgramBillings.Where(b => b.Amount > 0 && b.DeletedDate == null).Select(b => b.Amount).Sum()
               let totalCredits = (decimal?)p.ProgramBillings.Where(b => b.Amount < 0 && b.DeletedDate == null).Select(b => -b.Amount).Sum()
               let billingBalance = (totalCharges - totalCredits)

我不理解这个问题。 ProgramBilling.Amount 是一个非空的十进制数。如果我将鼠标悬停在 Sum() 调用上方,Intellisense 会显示它返回类型 Decimal。但是其他测试证实,在我的第二个版本中,当 ProgramBillings 没有数据时,totalChargestotalCredits 都被设置为 null。

问题:

  1. 我知道 Sum() 对于空集合返回 0。但是在什么情况下这不成立?

  2. 如果有时不成立,那么为什么当我将鼠标悬停在 Sum() 上方时,Intellisense 显示它返回类型 Decimal 而不是 Decimal?看起来 Intellisense 和我的理解是一致的。

编辑:

似乎有一个简单的解决方法,就是使用类似于 Sum() ?? 0m 的方法。但是这是非法的,会给我带来错误:

运算符“??”不能应用于类型为“decimal”和“decimal”的操作数


3
你看过这篇帖子吗:https://dev59.com/ZGMm5IYBdhLWcg3wdu1V - David Tansey
@JonathanWood:“但我仍然困惑为什么这是必要的”,请再次阅读链接问题的标题。如果集合为空,则这是必需的。如果不是null,您会期望空集合的总和是什么?显然,“0”不够好,因为“0”是总和的完全有效值。 - Matt Burland
@DavidTansey:我确实看到了,但它并没有提供我在问题结尾所问的任何线索。这是我将采取的方法,如果需要的话。只是想了解为什么。 - Jonathan Wood
尝试使用被接受的答案中的内容,在你的 Select 后添加一个 DefaultIfEmpty(0),然后再加上 Sum。现在如果你有一个空集合,你将得到 0,而 0 的总和当然是 0。 - Matt Burland
正如我在其他地方所指出的那样,我期望一个空集合的总和为0。而且,显然,就像我一样,Intellisense认为Sum()永远不会返回null。 "显然0并不够好,因为0是一个完全有效的总和值"。如果0是一个完全有效的总和值,那么为什么0不够好呢? - Jonathan Wood
@JonathanWood:因为空集合没有总和。正如Servy的答案所解释的那样,当您使用实体框架时,语义是不同的。显然,智能感知还不够聪明,无法区分它们之间的差异。 - Matt Burland
3个回答

10
我了解到,当集合为空时,Sum()返回0。什么情况下不是这样呢?当您没有使用LINQ to objects时,就像在这里一样。在这里,您有一个查询提供程序,它将此查询转换为SQL。 SQL操作的SUM运算符具有不同的语义。
如果有时候不是这样,那么为什么当我悬停在Sum()上时,Intellisense显示它返回Decimal类型而不是Decimal?看来Intellisense与我有相同的理解。
C# LINQ SUM运算符不返回可空值;它需要有非空值,但SQL SUM运算符具有不同的语义,当对空集求和时返回null而不是0。在C#要求非空值的上下文中提供null值是导致所有错误的原因。如果此处C# LINQ SUM运算符返回可空值,则可以轻松返回null。正是C#运算符和它用于表示的SQL运算符之间的差异导致了这个错误。

感谢你的解释。我原以为这与C#和SQL语义之间的差异有关,但看起来编译器在这里假定了C#语义的不足之处。如果没有这种假设,我本可以使用合并运算符将空返回值强制转换为零。 - Jonathan Wood
LINQ 无法支持任何地方、任何时候可能查询的每个单一语义。如果尝试这样做,它将变得非常复杂,几乎无法使用。因此,它需要对支持哪些操作、不支持哪些操作以及应该支持哪些语义做出某些决策。然后,查询提供程序有责任将可以在 LINQ 中描述的操作映射到其正在查询的内容中。现实情况是,并非所有内容都能够进行1:1的映射。 - Servy
我理解,但在这种情况下,似乎需要更少的严格类型构造。无论如何,我很难找到一个解释。这个问题得到了相当多的关注。再次感谢。 - Jonathan Wood

4

在我的EF查询中,当集合为空时,我遇到了同样的问题,解决方法之一是将其转换为可空的十进制数:

var total = db.PaiementSet.Sum(o => (Decimal?)o.amount) ?? 0M;

希望能帮到你。

0

在 .Sum 前添加 DefaultIfEmpty(0.0M)


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