如何在源集合为空时强制LINQ Sum()返回0


183

基本上,当我执行以下查询时,如果没有匹配线索,则以下查询将引发异常。在那种情况下,我希望总和等于0,而不是抛出异常。在查询本身中是否有可能-我的意思是要存储查询并进行检查query.Any()

double earnings = db.Leads.Where(l => l.Date.Day == date.Day
                && l.Date.Month == date.Month
                && l.Date.Year == date.Year
                && l.Property.Type == ProtectedPropertyType.Password
                && l.Property.PropertyId == PropertyId).Sum(l => l.Amount);

2
Where不回来null,如果它没有找到任何记录,它将返回零项的列表。有什么例外?
Mike Perrenoud 2013年

3
有什么例外?
Toto

3
我得到一个例外:转换为值类型'Int32'失败,因为实例化值为null。结果类型的通用参数或查询必须使用可为空的类型。
约翰·梅耶

1
@Stijn,没有,您所做的仍然无法成功。问题SQL是生成方法。Amount实际上不是null,这实际上是围绕如何处理零结果的问题。看看提供的答案。
Mike Perrenoud 2013年

39
您不应该对美元金额使用双倍偶数美元金额。绝对不要在打算使用确切量的情况下使用两倍。您的数据库列应为decimal,代码应使用decimal。忘记你所知道的floatdouble你的编程生涯,直到有一天有人告诉你使用它们,统计或星亮度或电子的随机过程或付费的结果!在那之前,您做错了
ErikE

Answers:


390

尝试将查询更改为此:

db.Leads.Where(l => l.Date.Day == date.Day
            && l.Date.Month == date.Month
            && l.Date.Year == date.Year
            && l.Property.Type == ProtectedPropertyType.Password
            && l.Property.PropertyId == PropertyId)
         .Select(l => l.Amount)
         .DefaultIfEmpty(0)
         .Sum();

这样,您的查询将仅选择该Amount字段。如果集合为空,它将返回一个值为的元素,0然后应用总和。


确实可以解决这个问题,但不是会先选择数量值列表,Sum然后在服务器端而不是数据库端选择它们吗?imo 2kay的解决方案更优化,至少在语义上更正确。
马克西姆六世。

3
@MaksimVI EF将在IQueryable<T>链停止时(通常在您调用ToListAsEnumerable,等等,在这种情况下Sum)时,在第一个实现时生成查询。 Sum是EF Queryable Provider已知并处理的方法,并将生成相关的SQL语句。
西蒙·贝兰格

我纠正了@SimonBelanger,总和是在数据库端进行的,但是是在首先选择金额的子查询中进行的。基本上查询SELECT SUM(a.Amount) FROM (SELECT Amount FROM Leads WHERE ...) AS a不是SELECT SUM(Amount) FROM Leads。子查询还具有附加的空检查和带有单个行表的怪异的外部联接。
马克西姆六世。

性能差异不大,并且可能已经过优化,但是我仍然认为其他解决方案看起来更干净。
马克西姆六世。

5
请注意,DefaultIfEmpty许多LINQ提供程序都不支持该功能,因此ToList()在这种情况下,您需要先放入或类似的东西,然后才能在LINQ to Objects场景中应用它。
克里斯托弗·金

188

我更喜欢使用另一个技巧:

double earnings = db.Leads.Where(l => l.Date.Day == date.Day
                                      && l.Date.Month == date.Month
                                      && l.Date.Year == date.Year
                                      && l.Property.Type == ProtectedPropertyType.Password
                                      && l.Property.PropertyId == PropertyId)
                          .Sum(l => (double?) l.Amount) ?? 0;

18
使用Linq进行SQL生成的SQL代码比接受的答案短得多
wertzui 2014年

3
这是正确的答案。所有其他人都失败了。首先转换为可空值,然后将最终结果与空值进行比较。
Mohsen Afshin

3
这比Linq To EF的公认答案要好得多。对我来说,生成的SQL的性能比DefaultIfEmpty好3.8倍。
弗洛里安

2
这是更快。
frakon

1
我不会称其为hack,因为这是为nullable设计的确切用途,即显示无数据和默认值之间的差异
MikeT


4

这对我来说是胜利:

int Total = 0;
Total = (int)Db.Logins.Where(L => L.id == item.MyId).Sum(L => (int?)L.NumberOfLogins ?? 0);

在我的LOGIN表中,在NUMBEROFLOGINS字段中,某些值为NULL,而其他值为INT。我在这里总结一个公司的所有用户的总数NUMBEROFLOGINS(每个ID)。


1

尝试:

double income = db.Leads.Where(l => l.ShouldBeIncluded).Sum(l => (double?) l.Amount)?0 ;

查询“ SELECT SUM([Amount]) ”将为空列表返回NULL。但是,如果您使用LINQ,它期望“ Sum(l => l.Amount) ”返回双精度值,并且不允许您使用“ ?? ”运算符将0设置为空集合。

为了避免这种情况,您需要使LINQ期望“ double? ”。您可以通过强制转换“ (double?)l.Amount ”来实现。

它不会影响对SQL的查询,但会使LINQ适用于空集合。


0
db.Leads.Where(l => l.Date.Day == date.Day
        && l.Date.Month == date.Month
        && l.Date.Year == date.Year
        && l.Property.Type == ProtectedPropertyType.Password
        && l.Property.PropertyId == PropertyId)
     .Select(l => l.Amount)
     .ToList()
     .Sum();

1
请添加一些信息,有关代码的答案
Jaqen H'ghar

1
当我尝试不使用ToList()时出现错误,因为它不返回任何内容。但是ToList()会成为一个空列表,当我执行ToList()。Sum()时不会给出任何错误。
莫娜

2
ToList如果您想要的只是总数,您可能不想在这里使用。这会将整个结果集(Amount在这种情况下仅针对每个记录)返回到内存中,然后返回该结果Sum()集。使用另一种通过SQL Server进行计算的解决方案更好。
Josh M.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.