如何强制填充DataRow?C# DataSet实现“最后保存获胜”方法
没问题,要实现你说的「最后保存获胜」——也就是让本地修改强制覆盖数据库中已经变更的数据,解决并发冲突的问题,你可以试试这几种方案,根据你的实际代码场景选就行:
方案1:通过ConflictOption配置自动生成更新命令
如果你是用OracleCommandBuilder自动生成Update/Insert/Delete命令的话,最简单的方式就是修改它的ConflictOption属性为OverwriteChanges。
默认情况下,CommandBuilder生成的Update语句会把所有原始字段值都放到WHERE子句里(用来对比数据库当前值,判断是否有并发修改),而设置OverwriteChanges后,WHERE子句只会包含主键列,这样不管数据库里的其他字段有没有变化,只要主键匹配就会用本地新值覆盖。
代码示例:
// 假设你已经有了查询用的OracleCommand和OracleConnection var selectCmd = new OracleCommand("SELECT UserId, Name, Age FROM Users", yourConnection); var adapter = new OracleDataAdapter(selectCmd); // 创建CommandBuilder并设置ConflictOption var builder = new OracleCommandBuilder(adapter); builder.ConflictOption = ConflictOption.OverwriteChanges; // 后续Fill和Update操作不变 var ds = new DataSet(); adapter.Fill(ds, "Users"); // 修改DataSet中的行... // 执行更新,此时会直接覆盖数据库中对应主键的行 adapter.Update(ds, "Users");
方案2:手动构造UpdateCommand(更灵活可控)
如果你不想依赖CommandBuilder,或者需要自定义更新逻辑,可以手动写Update语句,确保WHERE子句只包含主键,不对比其他原始字段值。
这样更新时只会根据主键定位行,直接用本地新值覆盖数据库数据:
var adapter = new OracleDataAdapter(); adapter.SelectCommand = new OracleCommand("SELECT UserId, Name, Age FROM Users", yourConnection); // 手动构造UpdateCommand var updateCmd = new OracleCommand( "UPDATE Users SET Name = :NewName, Age = :NewAge WHERE UserId = :OriginalUserId", yourConnection); // 绑定参数:新值用当前行的Current版本,主键用Original版本(防止主键被修改) updateCmd.Parameters.Add(":NewName", OracleType.VarChar, 50, "Name").SourceVersion = DataRowVersion.Current; updateCmd.Parameters.Add(":NewAge", OracleType.Int32, 0, "Age").SourceVersion = DataRowVersion.Current; updateCmd.Parameters.Add(":OriginalUserId", OracleType.Int32, 0, "UserId").SourceVersion = DataRowVersion.Original; adapter.UpdateCommand = updateCmd; // 后续Fill、修改、Update操作正常执行即可
方案3:在RowUpdated事件中捕获冲突并强制覆盖
如果你已经在处理RowUpdated事件,想在捕获到并发冲突时再强制执行覆盖操作,可以在事件处理逻辑里判断冲突,然后执行一个不检查原始值的更新命令。
通常并发冲突会表现为e.RecordsAffected == 0(因为默认的WHERE子句找不到匹配的行),或者抛出对应的Oracle异常(比如ORA-01403)。处理逻辑示例:
var adapter = new OracleDataAdapter(selectCmd); // 假设你已经有默认的CommandBuilder或UpdateCommand // 绑定RowUpdated事件 adapter.RowUpdated += (sender, e) => { if (e.Status == UpdateStatus.ErrorsOccurred) { var oracleEx = e.Exception as OracleException; // 判断是否是并发冲突:要么是没有找到行,要么是对应的异常码 if ((oracleEx != null && oracleEx.Number == 1403) || e.RecordsAffected == 0) { // 构造强制覆盖的更新命令 var forceUpdate = new OracleCommand( "UPDATE Users SET Name = :NewName, Age = :NewAge WHERE UserId = :UserId", e.Command.Connection); // 从当前行获取新值和原始主键 forceUpdate.Parameters.Add(":NewName", OracleType.VarChar, 50).Value = e.Row["Name", DataRowVersion.Current]; forceUpdate.Parameters.Add(":NewAge", OracleType.Int32).Value = e.Row["Age", DataRowVersion.Current]; forceUpdate.Parameters.Add(":UserId", OracleType.Int32).Value = e.Row["UserId", DataRowVersion.Original]; // 执行强制更新 int affected = forceUpdate.ExecuteNonQuery(); if (affected > 0) { // 标记冲突已处理,跳过默认的错误流程 e.Status = UpdateStatus.SkipCurrentRow; e.Exception = null; } } } }; // 后续Fill、修改、Update操作正常执行
注意事项
- 「最后保存获胜」逻辑会直接覆盖数据库中的最新数据,一定要确保业务场景允许这种操作,避免不可逆的数据丢失。
- 如果你的表主键允许被修改(非常不建议这么设计),那方案中主键的取值要特别注意,确保能正确定位到目标行。
内容的提问来源于stack exchange,提问作者Danny
相关产品推荐
相关产品推荐

