You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在EF Core 6中按可空DateTimeOffset属性分组

问题描述

实体类

public class Transaction
{
    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long Id { get; set; }

    [Required]
    public long UserId { get; set; }

    [Required]
    public DateTimeOffset? TransactAt { get; set; }

    ...

    [Required]
    public decimal? Amount { get; set; }
}

我需要按UserId和TransactAt.Date分组统计交易数据,编写的查询代码如下:

var transactions = await dbContext.Transactions
    .AsNoTracking()
    .Where(x.TransactAt.HasValue
        && x.TransactAt.Value.Date < DateTime.Today.Date)
    .GroupBy(x => new { x.UserId, x.TransactAt.Value.Date })
    .Select(m => new TransactionWithoutInvoiceModel
    {
        UserId = m.Key.UserId,
        TransactionIds = m.Select(x => x.Id).ToList(),
        Amounts = m.Select(x => x.Amount).ToList()
    })
    .ToListAsync();

执行时出现编译器错误:

Nullable value type may be null. csharp(CS8269)

尝试改用空传播运算符修改分组逻辑:

GroupBy(x => new { x.UserId, x.TransactAt?.Date })

又触发新的错误:

An expression tree lambda may not contain a null propagating operator. csharp(CS8072)

示例数据

Transactions

Id      UserId      Amount      TransactAt
------------------------------------------------------
1       123         100         2022-12-26 01:02:03+00
2       123         200         2022-12-26 01:05:03+00
3       123         300         2022-12-26 01:08:03+00
4       123         400         2022-12-26 01:10:03+00
5       123         500         2022-12-27 01:02:03+00
6       456         100         2022-12-26 01:02:03+00
7       456         200         2022-12-26 01:02:03+00

期望分组结果

[
   {
      "UserId":123,
      "TransactionIds":[1,2,3,4],
      "Amounts":[100,200,300,400]
   },
   {
      "UserId":123,
      "TransactionIds":[5],
      "Amounts":[500]
   },
   {
      "UserId":456,
      "TransactionIds":[6,7],
      "Amounts":[100,200]
   }
]

环境信息

  • .NET SDK 6.0.400
  • PostgreSQL 13.3
  • Npgsql.EntityFrameworkCore.PostgreSQL 6.0.7

解决方案

问题核心是:编译器无法自动识别Where子句已经过滤掉TransactAt为null的记录,因此在GroupBy中访问x.TransactAt.Value时仍提示空值风险;而空传播运算符?.无法被EF Core转换为SQL表达式树,所以也不可用。

以下三种方式均可解决问题:

方式1:使用空包容运算符明确告知编译器值非空

既然Where已经筛选出TransactAt.HasValue为true的记录,可以用!运算符强制告诉编译器该值不会为null:

var transactions = await dbContext.Transactions
    .AsNoTracking()
    .Where(x => x.TransactAt.HasValue && x.TransactAt.Value.Date < DateTime.Today.Date)
    .GroupBy(x => new { x.UserId, Date = x.TransactAt!.Value.Date })
    .Select(m => new TransactionWithoutInvoiceModel
    {
        UserId = m.Key.UserId,
        TransactionIds = m.Select(x => x.Id).ToList(),
        Amounts = m.Select(x => x.Amount).ToList()
    })
    .ToListAsync();

方式2:先投影再分组

通过Select提前提取非空的日期字段,再进行分组,让编译器明确字段非空:

var transactions = await dbContext.Transactions
    .AsNoTracking()
    .Where(x => x.TransactAt.HasValue && x.TransactAt.Value.Date < DateTime.Today.Date)
    .Select(x => new 
    {
        x.Id,
        x.UserId,
        x.Amount,
        TransactDate = x.TransactAt.Value.Date
    })
    .GroupBy(x => new { x.UserId, x.TransactDate })
    .Select(m => new TransactionWithoutInvoiceModel
    {
        UserId = m.Key.UserId,
        TransactionIds = m.Select(x => x.Id).ToList(),
        Amounts = m.Select(x => x.Amount).ToList()
    })
    .ToListAsync();

方式3:使用PostgreSQL专属日期截断函数(推荐)

针对PostgreSQL环境,使用EF Core提供的EF.Functions.DateTrunc函数直接在数据库层面截断日期,既避免空值问题,也能利用数据库索引提升性能:

var transactions = await dbContext.Transactions
    .AsNoTracking()
    .Where(x => x.TransactAt.HasValue && x.TransactAt < DateTime.Today)
    .GroupBy(x => new 
    { 
        x.UserId, 
        TransactDate = EF.Functions.DateTrunc("day", x.TransactAt!.Value)
    })
    .Select(m => new TransactionWithoutInvoiceModel
    {
        UserId = m.Key.UserId,
        TransactionIds = m.Select(x => x.Id).ToList(),
        Amounts = m.Select(x => x.Amount).ToList()
    })
    .ToListAsync();

注:x.TransactAt < DateTime.Today比比较Date属性更高效,数据库可直接利用时间字段的索引;DateTrunc("day", ...)的效果和Date属性一致,更符合PostgreSQL的语法规范。

内容的提问来源于stack exchange,提问作者Ed Wang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 16:45:25