[英]Entity Framework Core NodaTime Sum Duration
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。
您將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
在這里查看Npgsql.EntityFrameworkCore.PostgreSQL
的源代碼,您可以看到它無法翻譯Duration
的成員。 如果使用的成員屬於類型不是LocalDateTime
、 LocalDate
、 LocalTime
或Period
的對象,則Translate
方法將返回null
。 在您的情況下,使用的成員是TotalHours
並且它屬於類型為Duration
的對象。
因此,如果您將DurationActual
的類型從Duration
更改為Period
( TimeSpan
也可以使用),則可以使您的第二個示例起作用。
盡管如此, Period
的成員TotalHours
也無法翻譯(有關可以翻譯的成員的完整列表,請參閱此處的代碼)。
所以你必須自己計算這個值,如下所示:
await dbContext.Reports
.GroupBy(r => r.Date)
.Select(g => new
{
g.Key,
SummedDurationActual = g.Sum(r => r.DurationActual.Hours + ((double)r.DurationActual.Minutes / 60) + ((double)r.DurationActual.Seconds / 3600)),
})
.ToListAsync(cancellationToken)
如果無法更改DurationActual
的類型,您可以向 Npgsql 開發人員提出問題以添加必要的翻譯。 他們建議在他們的文檔中這樣做:
請注意,該插件遠未涵蓋所有翻譯。 如果缺少您需要的翻譯,請打開一個問題以請求它。
@mohamed-amazirh 的回答幫助我找到了正確的方向。 無法更改為 Period(因為它根本不是一個周期,而是一個持續時間)。 我最終編寫了一個IDbContextOptionsExtension
來滿足我的需求。
完整代碼在這里:
public class NpgsqlNodaTimeDurationOptionsExtension : IDbContextOptionsExtension
{
private class ExtInfo : DbContextOptionsExtensionInfo
{
public ExtInfo(IDbContextOptionsExtension extension) : base(extension) { }
public override long GetServiceProviderHashCode()
{
return 0;
}
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
{
return;
}
public override bool IsDatabaseProvider => false;
public override string LogFragment => "using NodaTimeDurationExt ";
}
public NpgsqlNodaTimeDurationOptionsExtension()
{
Info = new ExtInfo(this);
}
public void ApplyServices(IServiceCollection services)
{
new EntityFrameworkRelationalServicesBuilder(services)
.TryAddProviderSpecificServices(x => x.TryAddSingletonEnumerable<IMemberTranslatorPlugin, NpgsqlNodaTimeDurationMemberTranslatorPlugin>());
}
public void Validate(IDbContextOptions options) { }
public DbContextOptionsExtensionInfo Info { get; set; }
}
public class NpgsqlNodaTimeDurationMemberTranslatorPlugin: IMemberTranslatorPlugin
{
public NpgsqlNodaTimeDurationMemberTranslatorPlugin(ISqlExpressionFactory sqlExpressionFactory)
{
Translators = new IMemberTranslator[]
{
new NpgsqlNodaTimeDurationMemberTranslator(sqlExpressionFactory),
};
}
public IEnumerable<IMemberTranslator> Translators { get; set; }
}
public class NpgsqlNodaTimeDurationMemberTranslator : IMemberTranslator
{
private readonly ISqlExpressionFactory sqlExpressionFactory;
public NpgsqlNodaTimeDurationMemberTranslator(ISqlExpressionFactory sqlExpressionFactory)
{
this.sqlExpressionFactory = sqlExpressionFactory;
}
public SqlExpression Translate(SqlExpression instance, MemberInfo member, Type returnType, IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
var declaringType = member.DeclaringType;
if (instance is not null
&& declaringType == typeof(Duration))
{
return TranslateDuration(instance, member, returnType);
}
return null;
}
private SqlExpression? TranslateDuration(SqlExpression instance, MemberInfo member, Type returnType)
{
return member.Name switch
{
nameof(Duration.TotalHours) => sqlExpressionFactory
.Divide(sqlExpressionFactory
.Function("DATE_PART",
new[]
{
sqlExpressionFactory.Constant("EPOCH"),
instance,
},
true, new[] { true, true },
typeof(double)
),
sqlExpressionFactory.Constant(3600)
),
_ => null,
};
}
}
要使用它,我必須以與 NodaTime 相同的方式添加它:
services.AddDbContext<Cockpit2DbContext>(options =>
{
options
.UseLazyLoadingProxies()
.UseNpgsql(configuration.GetConnectionString("Cockpit2DbContext"),
o =>
{
o.UseNodaTime();
var coreOptionsBuilder = ((IRelationalDbContextOptionsBuilderInfrastructure) o).OptionsBuilder;
var ext = coreOptionsBuilder.Options.FindExtension<NpgsqlNodaTimeDurationOptionsExtension>() ?? new NpgsqlNodaTimeDurationOptionsExtension();
((IDbContextOptionsBuilderInfrastructure) coreOptionsBuilder).AddOrUpdateExtension(ext);
})
.ConfigureWarnings(w => w.Ignore(RelationalEventId.MultipleCollectionIncludeWarning));
}
);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.