生产环境SQL Server Upsert改造:添加Rowversion并发检查并抛出可捕获错误
这个需求很典型,我帮你拆解下具体实现步骤,既能用Rowversion做乐观锁校验,还能抛出C#能精准捕获的专属错误,给用户清晰的并发冲突提示:
核心思路:Rowversion乐观锁 + 自定义SQL错误抛出
通过给业务表添加Rowversion字段自动维护行版本,在Upsert存储过程中加入版本校验逻辑,当检测到并发冲突时抛出带特定标识的自定义错误,让C#端可以精准识别并转化为用户友好提示。
1. 给业务表添加Rowversion字段
首先确保你的业务表有一个Rowversion类型的字段(SQL Server会自动维护,每次行变更都会更新该值)。如果还没有,执行以下SQL添加:
ALTER TABLE YourBusinessTable ADD RowVersionStamp ROWVERSION;
2. 改造Upsert存储过程,加入并发校验+自定义错误抛出
修改原有存储过程,加入Rowversion校验逻辑,当校验失败(行已被其他用户修改)时,抛出带专属错误号的自定义错误(错误号需在50000以上,属于SQL Server预留的自定义错误区间):
CREATE PROCEDURE dbo.UpsertYourEntity @Id INT, @NewValue NVARCHAR(100), @CurrentRowVersion BINARY(8) -- 从C#端传入的当前行版本 AS BEGIN SET NOCOUNT ON; -- 带版本校验的更新操作 UPDATE YourBusinessTable SET YourColumn = @NewValue WHERE Id = @Id AND RowVersionStamp = @CurrentRowVersion; -- 判断更新是否生效 IF @@ROWCOUNT = 0 BEGIN -- 先区分“行不存在”和“并发冲突”两种情况 IF EXISTS(SELECT 1 FROM YourBusinessTable WHERE Id = @Id) BEGIN -- 抛出并发冲突专属错误,错误号用50001(可自定义) THROW 50001, '并发冲突:该数据已被其他用户修改,请刷新后重试。', 1; END ELSE BEGIN -- 行不存在则执行插入逻辑 INSERT INTO YourBusinessTable(Id, YourColumn) VALUES(@Id, @NewValue); END END END
注:SQL Server 2016及以上版本推荐用
THROW替代RAISERROR,它默认错误级别为16(用户可捕获),还会保留错误堆栈便于调试。
3. C#端精准捕获并发冲突错误
在调用存储过程的C#代码中,捕获SqlException并检查错误号,识别出并发冲突后给用户友好提示:
// 可定义常量统一错误号,避免硬编码不一致 private const int ConcurrencyConflictErrorNumber = 50001; try { using (var conn = new SqlConnection("YourConnectionString")) { conn.Open(); using (var cmd = new SqlCommand("dbo.UpsertYourEntity", conn)) { cmd.CommandType = CommandType.StoredProcedure; cmd.Parameters.AddWithValue("@Id", targetEntityId); cmd.Parameters.AddWithValue("@NewValue", updatedValue); // 传入之前查询获取的当前行版本 cmd.Parameters.AddWithValue("@CurrentRowVersion", currentRowVersion); cmd.ExecuteNonQuery(); } } } catch (SqlException ex) { // 检查是否是并发冲突专属错误 if (ex.Errors.Cast<SqlError>().Any(e => e.Number == ConcurrencyConflictErrorNumber)) { // 给用户显示友好提示 MessageBox.Show(ex.Message, "操作提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); // 可选:自动刷新数据,让用户看到最新版本 RefreshCurrentData(); } else { // 其他SQL错误按原有逻辑处理 throw; } }
额外优化建议
- 插入新行后,可将自动生成的Rowversion值返回给C#端,方便后续编辑时携带版本信息;
- 可以把错误消息也封装成常量,SQL和C#端统一使用,避免文案不一致;
- 如果是分布式系统,可结合事务确保校验和更新的原子性。
内容的提问来源于stack exchange,提问作者pStan
相关产品推荐
相关产品推荐

