EF Core查询DateTimeOffset的DateTime部分无法转换如何解决?
报错原因
System.InvalidOperationException: The LINQ expression 'DbSet().Where(t => True && t.BeginTime.DateTime >= __fakeStartDate_1 && t.BeginTime.DateTime < __fakeEndDate_2)' could not be translated.
该报错是因为EF Core默认没有提供DateTimeOffset.DateTime属性的SQL Server转换映射,LINQ provider无法将该属性访问逻辑转成对应SQL语句,所以抛出异常。标记[NotMapped]的属性仅会在客户端内存中求值,无法参与SQL翻译,依然会触发全表数据拉取,不符合性能要求。
可行解决方案
方案1:使用SQL函数映射查询(无需修改表结构)
EF Core 5.0及以上版本支持内置转换函数调用,可直接在LINQ中实现需求,适用于任意时间范围查询:
// 按指定日期查询写法 DateTime queryDay = new DateTime(2021, 1, 1); var results = dbContext.Appointments .Where(a => EF.Functions.Convert<DateTime>(a.BeginTime) >= queryDay && EF.Functions.Convert<DateTime>(a.BeginTime) < queryDay.AddDays(1)) .ToList();
该写法会被自动翻译为SQL Server的CONVERT(datetime2, BeginTime)逻辑,完全在数据库端执行过滤,性能不受影响。
方案2:添加计算列属性(复用性更高)
如果需要频繁做这类查询,可以在模型中配置计算列,后续查询直接使用该属性即可:
- 模型配置代码:
modelBuilder.Entity<Appointment>() .Property<DateTime>("BeginTimeLocal") .HasComputedColumnSql("CONVERT(datetime2, BeginTime)") .IsRequired();
- 查询写法:
var results = dbContext.Appointments .Where(a => EF.Property<DateTime>(a, "BeginTimeLocal") >= queryDay && EF.Property<DateTime>(a, "BeginTimeLocal") < queryDay.AddDays(1)) .ToList();
你也可以直接在实体类中添加对应属性,避免每次用EF.Property访问:
public class Appointment { public int Id {get;set;} public DateTimeOffset BeginTime {get;set;} public DateTime BeginTimeLocal {get;set;} }
配置时直接将BeginTimeLocal关联到上述计算列逻辑即可。
方案3:直接执行原生SQL
低版本EF Core或者复杂查询场景下,可直接写原生SQL实现过滤:
var results = dbContext.Appointments .FromSqlInterpolated($"SELECT * FROM Appointments WHERE CONVERT(datetime2, BeginTime) >= {queryDay} AND CONVERT(datetime2, BeginTime) < {queryDay.AddDays(1)}") .ToList();
注意事项
不要通过加AsEnumerable()、ToList()等方式走客户端评估,该操作会将全表数据拉取到内存再过滤,数据量大时性能会非常差。
内容的提问来源于stack exchange,提问作者Tamer Rifai

