为了账号安全,请及时绑定邮箱和手机立即绑定

在 EF Core 中适用时强制 SUM 方法返回 NULL

在 EF Core 中适用时强制 SUM 方法返回 NULL

C#
慕勒3428872 2023-07-09 17:05:56
我正在执行一个查询,其中我偶尔期望 NULL,如下所示:.Where(d => d.Id == varid && d.Date >= vardate1 && d.Date <= vardate2) .Sum(d => (decimal?)d.Delta);Delta 是一个不可为空的小数,并且智能感知显示 Sum 的结果将是一个小数?因为我介绍了演员阵容。生成的 SQL 符合预期,并且当手动运行时,如果没有匹配的记录,它会正确返回 NULL。但是,具体化查询的结果始终为 0。此行为与非核心 EF 不同,后者会返回 null。这真的是新的预期行为吗?如果是这样,我怎样才能在需要时强制它返回 null?Null 和 0 在这种情况下具有不同的含义。我可以先引入记录,然后在服务器上求和,但如果 EF core 能够自行实现我所期望的功能,那就太好了。
查看完整描述

2 回答

?
慕田峪7331174

TA贡献1828条经验 获得超13个赞

最有可能是一个错误,但了解 EF Core 设计者对不可空Max//和/翻译的愿景,如果他们故意这样做是为了模拟(奇怪的)LINQ to ObjectsMin可空行为(通过结果返回事件),我不会感到惊讶方法的类型可以为空。 AverageFirstSingleSum0

通过下面的片段可以看出

decimal? result = Enumerable.Empty<decimal?>().Sum(); // result is 0

甚至有记录(!?):

评论

source如果不包含任何元素,此方法返回零。

“有趣”的是,这仅适用于根查询Sum执行 - 在投影内部它具有您正在寻找的 SQL 行为。

这导致我们通过利用常量技巧与投影相结合的分组来找到解决方法。为了不在您需要的地方重复它,并且如果它在某些更高的 EF Core 版本中得到修复,也可以轻松删除它,您可以将其封装在自定义扩展方法中,如下所示:

public static partial class EfCoreExtensions

{

    public static decimal? SumOrDefault<T>(this IQueryable<T> source, Expression<Func<T, decimal?>> selector)

        => source.GroupBy(e => 0, selector).Select(g => g.Sum()).AsEnumerable().FirstOrDefault();

}

并替换


.Sum(d => (decimal?)d.Delta);


.SumOrDefault(d => d.Delta);

只需确保仅将其用于最终调用,因为如果您在查询表达式树中使用它,则作为任何自定义方法,它将无法被识别,并将导致客户端评估或运行时异常。


查看完整回答
反对 回复 2023-07-09
?
繁花不似锦

TA贡献1851条经验 获得超4个赞

上述“按常量分组技巧”在 EF Core 5.0 中不起作用。


使用聚合函数的扩展方法的变体可以实现所需的结果。因此,要让所有 NULL 返回 Null,否则返回非 null 值的总和:


    public static decimal? SumOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal?> selector)

        => (from s in source select selector(s))

           .Aggregate((decimal?)null, (acc, item) => acc.HasValue ? acc + item.GetValueOrDefault() : item);

或者,如果您希望任何 NULL 值都会返回 NULL


    public static decimal? SumAllOrNull<TSource>(this IEnumerable<TSource> source, Func<TSource, decimal?> selector)

        => (from s in source select selector(s))

           .Aggregate((decimal?)null, (acc, item) => acc.HasValue ? acc + item : item.HasValue ? item : null);

但请注意,如上所述,这仅适用于 Linq-to-Objects,不适用于 Linq-to-Sql,因此您需要事先使用 ToList() 或 AsEnumerable(),因此它会从数据库中带回您可能想要的更多数据或需要。


.Where(d => d.Id == varid && d.Date >= vardate1 && d.Date <= vardate2)

.AsEnumerable()

.SumOrDefault(d => d.Delta);


查看完整回答
反对 回复 2023-07-09
  • 2 回答
  • 0 关注
  • 206 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信