EF Core中合并Date与毫秒数并解决日期溢出问题
背景
我需要检查两个日期是否重叠,但数据库表未使用datetime类型,而是拆分用date和bigint类型存储时间信息,因此得在C#代码中将它们合并为DateTime进行判断。
数据库表列
- StartDate: date(可为空)
- EndDate: date(可为空)
- AvailableFromMs: bigint(可为空)
- AvailableToMs: bigint(可为空)
实体类TEntity
[Column(TypeName = "date")] public DateTime? StartDate { get; set; } [Column(TypeName = "date")] public DateTime? EndDate { get; set; } public TimeSpan? AvailableFromMs { get; set; } public TimeSpan? AvailableToMs { get; set; }
当前查询代码
IQueryable<TEntity> query = <repository_to_query_from>... DateTime routeStart = DateTime.Now; List<TEntity> overlapping = query .Where(r => routeStart <= ((DateTime)(object)r.EndDate.Value).AddSeconds(r.AvailableToMs == null ? 0.0 : r.AvailableToMs.Value.Milliseconds / 1000.0)) .ToList();
注:我知道这只是条件的一部分,等EF生成正确的SQL后我会更新它
EF Core生成的SQL
WHERE... (@__routeStart_1 <= DATEADD(second, CAST(CASE WHEN [r].[AvailableToMs] IS NULL THEN 0.0E0 ELSE CAST(DATEPART(millisecond, [r].[AvailableToMs]) AS float) / 1000.0E0 END AS int), CAST([r].[EndDate] AS datetime2)))
执行错误
执行查询时抛出错误:
Arithmetic overflow error converting expression to data type datetime.
问题原因
AvailableToMs的类型处理逻辑不符合预期:C#代码中是先将毫秒数除以1000转成秒,但EF生成的SQL却是先把DATEPART取到的毫秒值转成float再除以1000,之后才转成int传给DATEADD。这种顺序导致了溢出,我需要让EF先执行除法,再转成int。
解决方案
方案1:修正TimeSpan属性的取值逻辑
之前代码中使用Milliseconds是错误的,它仅取TimeSpan中0-999的毫秒部分,而数据库存储的是总毫秒数。应改用TotalMilliseconds,并采用整数除法避免浮点转换:
List<TEntity> overlapping = query .Where(r => routeStart <= r.EndDate.Value.AddSeconds(r.AvailableToMs == null ? 0 : r.AvailableToMs.Value.TotalMilliseconds / 1000)) .ToList();
这种写法会让EF生成先做除法、再转int的SQL,避免转换顺序问题。
方案2:使用EF.Functions直接控制SQL结构
如果方案1仍不生效,可通过EF.Functions直接构造DateAdd逻辑,强制按预期顺序执行:
List<TEntity> overlapping = query .Where(r => routeStart <= EF.Functions.DateAdd( Microsoft.EntityFrameworkCore.SqlServer.DateParts.Second, r.AvailableToMs == null ? 0 : (int)(r.AvailableToMs.Value.TotalMilliseconds / 1000), r.EndDate.Value)) .ToList();
方案3:修正实体类属性类型(推荐)
数据库中AvailableFromMs和AvailableToMs是bigint类型,存储的是总毫秒数,实体类用TimeSpan?映射易导致解析错误。应改为long?类型,匹配数据库存储逻辑:
[Column(TypeName = "date")] public DateTime? StartDate { get; set; } [Column(TypeName = "date")] public DateTime? EndDate { get; set; } public long? AvailableFromMs { get; set; } public long? AvailableToMs { get; set; }
对应的查询代码:
List<TEntity> overlapping = query .Where(r => routeStart <= r.EndDate.Value.AddSeconds(r.AvailableToMs == null ? 0 : r.AvailableToMs.Value / 1000)) .ToList();
这种类型匹配更直接,EF会生成完全符合预期的SQL:先对bigint值做除法,再转int传给DATEADD,彻底解决溢出问题。
内容的提问来源于stack exchange,提问作者T Sl

