【知识点】IQueryable.SumAsync方法的NULL异常

  进行这样一个查询,统计用户积分(point是int类型字段),代码很简单,结果却报错了:
1 await DbContext.Set<GetPointDetail>().Where(p => p.OwnerId == User.Id).SumAsync(p => p.Point);
 
  日志显示,查询结果为null,转换为int类型失败,必须使用可空类型。好吧,看到结果为null我就知道问题出在哪了。
  先来看看该行代码生成的sql语句:
1 exec sp_executesql N'SELECT 
2     [GroupBy1].[A1] AS [C1]
3     FROM ( SELECT 
4         SUM([Extent1].[Point]) AS [A1]
5         FROM [dbo].[GetPointDetails] AS [Extent1]
6         WHERE [Extent1].[OwnerId] = @p__linq__0
7     )  AS [GroupBy1]',N'@p__linq__0 varchar(8000)',@p__linq__0='xxx'
8 go
  熟悉sql server的应该知道,sum函数是会过忽略NULL值而非作为0参与计算,而如果查询结果没有一条数据,会直接返回NULL,我遇到的正是这种情况。而代码中的IQueryable.SumAsync()返回值是int,就是说,内部会将数据库返回的NULL值转换为int,显然就会报错。
  那么如何修改呢?先看第一种方案,使用DefaultIfEmpty方法:
1 await DbContext.Set<GetPointDetail>().Where(p => p.OwnerId == User.Id).Select(p => p.Point).DefaultIfEmpty().SumAsync();
 1 exec sp_executesql N'SELECT 
 2     [GroupBy1].[A1] AS [C1]
 3     FROM ( SELECT 
 4         SUM([Join1].[A1]) AS [A1]
 5         FROM ( SELECT 
 6             CASE WHEN ([Project1].[C1] IS NULL) THEN 0 ELSE [Project1].[Point] END AS [A1]
 7             FROM   ( SELECT 1 AS X ) AS [SingleRowTable1]
 8             LEFT OUTER JOIN  (SELECT 
 9                 [Extent1].[Point] AS [Point], 
10                 cast(1 as tinyint) AS [C1]
11                 FROM [dbo].[GetPointDetails] AS [Extent1]
12                 WHERE [Extent1].[OwnerId] = @p__linq__0 ) AS [Project1] ON 1 = 1
13         )  AS [Join1]
14     )  AS [GroupBy1]',N'@p__linq__0 varchar(8000)',@p__linq__0='xxx'
15 go
  DefaultIfEmpty()会对集合元素做空判断,为null就返回默认值,生成的sql表现为case when判断,不过这种sql明显不是一个好sql。
  那么从日志的错误信息入手,日志已经告诉我们,“must use a nullable type”,要使用int的可空类型,所以第二种方案就是对point字段做个“int?”显示转换:
1 await DbContext.Set<GetPointDetail>().Where(p => p.OwnerId == User.Id).SumAsync(p => (int?)p.Point);
  生成的sql同最初一样。这样无论结果是整数还是NULL,都能正确转换。
 
  提一句,可空值类型也是值类型,两者转换不会进行装箱/拆箱。

posted @ 2019-08-23 20:24  linys2333  阅读(497)  评论(0编辑  收藏  举报