EF Core 8 默认不支持 Oracle MERGE,因其仅生成标准 ANSI SQL(INSERT/UPDATE),不识别 Oracle 特有语法;Oracle.EntityFrameworkCore 8.x 也未重写 SaveChanges 为 MERGE,需通过 ExecuteSqlRaw 手动执行参数化 MERGE 语句,并注意 ON 列不可在 UPDATE 中修改、null 值需显式转 DBNull 等细节。

Merge 操作在 .NET 8 中无法直接通过 EF Core 原生支持——Oracle 的 MERGE INTO 不在 EF Core 标准语法中,必须绕过 ORM 层手写 SQL 或借助 Oracle 官方扩展。
为什么 EF Core 8 默认不支持 Oracle MERGE
EF Core 的 SaveChanges() 和变更跟踪机制只生成标准 ANSI SQL 的 INSERT/UPDATE,不识别 Oracle 特有的 MERGE INTO 语法。即使你用的是 Oracle 数据提供程序(如 Oracle.EntityFrameworkCore),它也仅在“批量插入”等少数场景下做优化,MERGE 仍需显式调用。
- Oracle.EntityFrameworkCore 8.x 不重写
SaveChanges()为MERGE—— 它不会自动把 Upsert 转成MERGE -
ExecuteSqlRaw()可以执行,但参数绑定、事务上下文、错误映射需手动处理 - 没有
DbContext.MERGE()这样的 API;所谓“支持”仅指“允许你安全地发一条 MERGE 语句”
正确调用 Oracle MERGE 的三种方式
推荐按优先级排序:能用原生 SQL 就不用封装;能用参数化就不用字符串拼接;涉及多行时优先建临时表再 MERGE。
- 用
Database.ExecuteSqlRaw()执行完整MERGE INTO语句,ON条件列不能出现在UPDATE SET中(否则报ORA-00904) - 若需动态构建,用
OracleParameter显式传参,避免 SQL 注入;例如:new OracleParameter("@id", id) - 对大批量数据,先
INSERT /*+ APPEND */到全局临时表,再从该表USING—— 避免应用层反复拼 SQL 字符串
常见错误:ORA-00904 / ORA-38104 / 参数绑定失败
这些错误几乎都源于语法或上下文误用,不是驱动问题。
-
ORA-00904: "T1"."ID": invalid identifier:通常因在WHEN MATCHED THEN UPDATE SET中更新了ON子句里出现的列(如SET t1.id = ...),Oracle 禁止这样做 -
ORA-38104: Columns referenced in the ON Clause cannot be updated in the UPDATE clause:同上,是明确的语法限制,不是 bug - 参数未绑定或类型不匹配(如 C#
int?传给NUMBER(6)字段但值为null)→ 报ORA-01008: not all variables bound或隐式转换失败
一个安全可用的 MERGE 示例(.NET 8 + Oracle.EntityFrameworkCore)
假设你要同步 users 表,主键为 user_id,根据邮箱去重:
context.Database.ExecuteSqlRaw(@"
MERGE INTO users t
USING (SELECT @email AS email, @name AS name, @age AS age FROM DUAL) s
ON (t.email = s.email)
WHEN MATCHED THEN
UPDATE SET t.name = s.name, t.age = s.age
WHEN NOT MATCHED THEN
INSERT (user_id, email, name, age)
VALUES (users_seq.NEXTVAL, s.email, s.name, s.age)",
new OracleParameter("@email", user.Email),
new OracleParameter("@name", user.Name),
new OracleParameter("@age", user.Age ?? (object)DBNull.Value));注意:DUAL 是必须的;users_seq.NEXTVAL 是 Oracle 序列;?? (object)DBNull.Value 是为 null 值正确绑定的关键。
真正难的不是写这条语句,而是确保它跑在正确的事务隔离级别下、和上下文其他操作不冲突、出错时能精准定位哪一行导致 LOG ERRORS 触发——这些细节比语法本身更容易被忽略。


















