为什么EF Core在数据库优先模型中将SQL Server的日期列映射为DateTime,而不是DateOnly?
我有一个SQL Server表,其列定义为:
date NULL
对应的实体类将此列映射到一个类型为 DateOnly? 的属性。应用中没有定义自定义转换器。
然而,将这个特定表列映射到这个特定属性时,会出现错误:
InvalidCastException: 无法将对象类型 'System.DateTime' 转换为类型 'System.DateOnly'
即使在覆盖 ConfigureConventions() 在 DbContext 里通过以下内容也仍然会出现此错误:
configBuilder.Properties<DateOnly?>().HaveColumnType("date");
这让人困惑,因为 EntityFramework Core自 8.0版本起就已支持 DateOnly。
在数据库优先(database-first)场景下,是否我忽略了某个将SQL Server date 列类型映射到 DateOnly 的配置项?
带代码示例的编辑:
MyEntity.cs:
[Table("ExistingTableName")]
[Keyless]
public class MyEntity
{
[Column("ExistingColumnName")]
public required DateOnly? Date {get; set;}
}
AppDbContext.cs:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}
public DbSet<MyEntity> MyTable {get; set;}
}
调用方:
public async Task GetEntity(AppDbContext db)
{
var data = await db.MyTable.Select(row => row.Date).FirstAsync(); // error thrown here.
}
附加说明:
- 在一个独立的项目和一个空本地数据库上尝试复现此问题时并未出现错误。我开始怀疑可能是数据库问题(因为原始项目连接的是一个已有数据库,SQL Server版本未知),但查看列属性后,发现本地测试数据库与现有数据库之间几乎没有差异。
- 为了保险起见,我删除了原始项目中的所有其他实体类和
DbSet,即使它们并未在查询中被引用。结果未见改变。 - 我新建了一个全新项目,但仍然使用指向现有数据库的原始连接字符串。结果和之前一样,出现了
InvalidCastException。 - 我已确认数据库版本为15.0.4365.2。不确定这是否会影响问题。
解决方案
我感觉自己像个傻瓜,但我想我已经找到了原因:
我在连接字符串中没有指定 Initial Catalog。
也许EF Core会选择一个与我要映射的列同名的数据库(例如服务器上存在的一个极其陈旧的数据库拷贝),而那个数据库中的该列的数据类型映射到了 DateTime,而不是 DateOnly。
让我意识到问题的线索来自一次复现实验:我在其中写下了一个指向 mssqllocaldb 的连接字符串,但忘了同样在其中添加 "Initial Catalog"。当我执行 Update-Database 迁移命令时,Entity Framework Core高兴地宣布SQL语句已正确执行,一切正常。然而我在服务器上检查时找不到该数据库。我原本期望EF Core会自动创建它,并像我过去看到的那样按应用程序命名。
后来我意识到原始应用的连接字符串中没有指定 "Initial Catalog" 时,便想到也许EF Core选错了数据库,并且并没有发出警告——就像我在测试中运行迁移时那样。更新连接字符串后快速重启,确认这就是解决问题所需的一切。
我猜我惊讶的根源在于,通常我会期望EF Core对未在服务器中明确声明要使用的数据库而产生的歧义发出异常。
此外,当我把所有的 DateOnly 类型改为 DateTime,并返回完整实体(而不是仅选择某一列)时,数据看起来也完全正常。除了时间部分仍然存在(且如同从日期到日期时间的转换所期望的那样设为午夜),其他列与我以为在使用的数据库中的列完全匹配。
我不太确定这个问题和回答对其他人有多大帮助——这并非EF Core内部数据映射方式的问题(正如标题所暗示的那样),因此我把问题的走向交给大多数人(以及管理员)的意见。
感谢大家的尝试和帮助;对于浪费你们的时间,我表示抱歉。