【发布时间】:2021-09-30 16:18:22
【问题描述】:
EF Core中下面的sql怎么写
select r."Date", sum(r."DurationActual")
from public."Reports" r
group by r."Date"
我们有以下模型 (mwe)
public class Report
{
public LocalDate Date { get; set; }
public Duration DurationActual { get; set; }
}
我尝试了以下方法:
await dbContext.Reports
.GroupBy(r => r.Date)
.Select(g => new
{
g.Key,
SummedDurationActual = g.Sum(r => r.DurationActual),
})
.ToListAsync(cancellationToken);
但这不会编译,因为Sum 仅适用于int、double、float、Nullable<int> 等。
我还尝试总结总小时数
await dbContext.Reports
.GroupBy(r => r.Date)
.Select(g => new
{
g.Key,
SummedDurationActual = g.Sum(r => r.DurationActual.TotalHours),
})
.ToListAsync(cancellationToken)
可以编译但不能被 EF 翻译并出现以下错误
System.InvalidOperationException: The LINQ expression 'GroupByShaperExpression:
KeySelector: r.Date,
ElementSelector:EntityShaperExpression:
EntityType: Report
ValueBufferExpression:
ProjectionBindingExpression: EmptyProjectionMember
IsNullable: False
.Sum(r => r.DurationActual.TotalHours)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', ....
当然我可以早点列举出来,但这效率不高。
进一步澄清一下:我们使用Npgsql.EntityFrameworkCore.PostgreSQL 和Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime 来建立连接。 Duration 是来自 NodaTime 的 DataType,表示类似于 TimeSpan 的东西。
Duration 被映射到数据库端的 interval。
我们大量使用使用 InMemoryDatabase (UseInMemoryDatabase) 的单元测试,因此该解决方案应该适用于 PsQl 和 InMemory。
对于那些不熟悉 NodaTime 的 EF-Core 集成的人:
你在配置中添加UseNodaTime()方法调用,例子:
services.AddDbContext<AppIdentityDbContext>(
options => options
.UseLazyLoadingProxies()
.UseNpgsql(configuration.GetConnectionString("DbConnection"),
o => o
.MigrationsAssembly(Assembly.GetAssembly(typeof(DependencyInjection))!.FullName)
.UseNodaTime()
)
这为 NodaTime 类型添加了类型映射
.AddMapping(new NpgsqlTypeMappingBuilder
{
PgTypeName = "interval",
NpgsqlDbType = NpgsqlDbType.Interval,
ClrTypes = new[] { typeof(Period), typeof(Duration), typeof(TimeSpan), typeof(NpgsqlTimeSpan) },
TypeHandlerFactory = new IntervalHandlerFactory()
}.Build()
我不知道每个细节,但我认为这增加了一个 ValueConverter。 更多信息:https://www.npgsql.org/efcore/mapping/nodatime.html
【问题讨论】:
-
数据库中
DurationActual列的实际类型是什么?你的模型需要与之匹配。然后,您可以拥有一个未映射的属性,将原始类型转换为Duration并返回。 -
实际类型为
interval。它由 ef 在Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime的帮助下自动映射我们可以在 C# 中使用 Duration 没问题,一切正常。我关心的是在数据库中总结它们。 -
您是否在
DurationActual上配置了任何值转换器? -
不,我没有直接。
Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime应该加一个。我已经更新了问题以进一步解释 -
您会对原始 SQL 实现不满意吗?鉴于 NodaTime 高度特定于 postgres 并且您使用的是非标准类型(Duration 而不是 Timespan),如果不为此数据类型编写自己的 linq 表达式处理程序或要求 NodaTime 开发人员这样做,则没有其他方法可以优化它.
标签: c# postgresql entity-framework-core nodatime