EF Core使用RowVersion遇InvalidCastException,Oracle替代方案咨询
Oracle 下替代 EF Core [Timestamp](RowVersion)的可行方案
Oracle 并不支持 Entity Framework 中用于乐观并发的 [Timestamp] 特性(对应 SQL Server 的 RowVersion 类型),你遇到的 InvalidCastException: Unable to cast object of type 'System.DBNull' to type 'System.Byte[]' 错误,本质是现有数据中该 Raw 类型字段为 NULL,EF 无法将其转换为 byte[] 类型。以下是几种成熟的替代方案:
方案一:时间戳字段 + Oracle 触发器(推荐)
这是最常用的替代方式,利用 Oracle 的时间戳和触发器自动维护版本:
- 修改实体类:移除
[Timestamp]属性,添加时间戳字段
public class Order { // 其他字段... public DateTime LastModified { get; set; } }
- 修改数据库表:添加对应字段,设置默认值为当前系统时间
ALTER TABLE ORDERS ADD LAST_MODIFIED TIMESTAMP(6) DEFAULT SYSTIMESTAMP;
- 创建触发器:确保每次更新时自动更新时间戳
CREATE OR REPLACE TRIGGER TRG_ORDERS_LAST_MODIFIED BEFORE UPDATE ON ORDERS FOR EACH ROW BEGIN :NEW.LAST_MODIFIED := SYSTIMESTAMP; END; /
- EF 配置并发检查:在
DbContext的OnModelCreating中标记该字段为并发令牌
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Order>() .Property(o => o.LastModified) .IsConcurrencyToken(); }
后续更新时,EF 会自动对比 LastModified 值,若不一致则抛出并发冲突异常。
方案二:自增数字版本号 + 触发器
如果对时间戳精度有顾虑,或者更倾向于数字版本管理,可以用自增版本号:
- 修改实体类:添加整数类型的版本字段
public class Order { // 其他字段... public int Version { get; set; } }
- 修改数据库表:添加版本字段,默认值设为 1
ALTER TABLE ORDERS ADD VERSION NUMBER(10, 0) DEFAULT 1;
- 创建触发器:每次更新时将版本号加 1
CREATE OR REPLACE TRIGGER TRG_ORDERS_VERSION BEFORE UPDATE ON ORDERS FOR EACH ROW BEGIN :NEW.VERSION := :OLD.VERSION + 1; END; /
- EF 配置并发检查:同样标记为并发令牌
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Order>() .Property(o => o.Version) .IsConcurrencyToken(); }
方案三:手动维护版本字段(不推荐)
如果不想用触发器,可以在业务逻辑中手动更新版本值,但这种方式容易遗漏,可靠性较低:
- 实体类添加时间戳或数字版本字段
- 每次执行更新操作时,手动将版本字段设为当前时间或自增后的值
- EF 中同样配置
IsConcurrencyToken()
临时解决当前的 DBNull 错误
如果暂时不想替换字段类型,可以先批量更新现有数据的 Raw 字段,给所有 NULL 值设置一个默认值:
UPDATE ORDERS SET VERSION = UTL_RAW.CAST_TO_RAW('0') WHERE VERSION IS NULL;
但这只是临时方案,长期来看还是建议换成上述两种触发器方案,更符合 Oracle 的生态和 EF 的并发检查逻辑。
内容的提问来源于stack exchange,提问作者SuperNev
相关产品推荐
相关产品推荐

