使用Dapper更新SQL Server中列值不同的交易数据行的实现方案
实现仅更新Transaction对象差异列的SQL Server方法
嘿,针对你开发的银行对账单PDF转SQL Server工具里的「仅更新Transaction对象差异列」需求,我整理了几个实用的实现方案,你可以根据自己的技术栈来选:
方案1:手动构建动态SQL(简单场景首选)
如果你的Transaction字段不多,手动对比字段并生成只包含变化列的UPDATE语句是最直观的方式,完全可控:
public void UpdateTransactionIfChanged(Transaction updatedTransaction) { // 1. 从数据库获取当前交易记录 var existingTransaction = _dbContext.Transactions .FirstOrDefault(t => t.TransactionID == updatedTransaction.TransactionID); if (existingTransaction == null) { // 可根据业务需求改为插入新记录或抛出异常 throw new ArgumentException("目标交易记录不存在于数据库中"); } // 2. 逐个字段对比,收集需要更新的内容 var updateClauses = new List<string>(); var sqlParameters = new List<SqlParameter>(); // 对比账号 if (existingTransaction.accountNumber != updatedTransaction.accountNumber) { updateClauses.Add("accountNumber = @AccountNumber"); sqlParameters.Add(new SqlParameter("@AccountNumber", updatedTransaction.accountNumber)); } // 对比交易日期(注意处理DateTime精度差异,比如截断毫秒) if (existingTransaction.TransDate != updatedTransaction.TransDate) { updateClauses.Add("TransDate = @TransDate"); sqlParameters.Add(new SqlParameter("@TransDate", updatedTransaction.TransDate)); } // 对比交易标题(忽略大小写的字符串对比) if (!string.Equals(existingTransaction.Title, updatedTransaction.Title, StringComparison.OrdinalIgnoreCase)) { updateClauses.Add("Title = @Title"); sqlParameters.Add(new SqlParameter("@Title", updatedTransaction.Title)); } // 继续对比Summary、category(注意你代码里的拼写是catagory,建议修正)、typeOfTransaction、金额等字段... // 3. 有需要更新的列才执行SQL if (updateClauses.Count > 0) { var updateSql = $"UPDATE Transactions SET {string.Join(", ", updateClauses)} WHERE TransactionID = @TransactionID"; sqlParameters.Add(new SqlParameter("@TransactionID", updatedTransaction.TransactionID)); _dbContext.Database.ExecuteSqlRaw(updateSql, sqlParameters.ToArray()); } }
优缺点:
- ✅ 逻辑清晰,完全控制更新内容,避免不必要的字段写入
- ❌ 字段较多时代码冗余,需要手动维护每个字段的对比逻辑
方案2:利用Entity Framework Core的变更跟踪(EF Core用户首选)
如果你用EF Core作为ORM框架,它自带的变更跟踪机制会自动检测实体属性的变化,生成只包含修改列的UPDATE语句,无需手动写SQL:
public void UpdateTransactionWithEFChangeTracker(Transaction updatedTransaction) { // 附加主键实体到上下文,告知EF需要跟踪该记录 var trackedTransaction = _dbContext.Transactions.Attach(new Transaction { TransactionID = updatedTransaction.TransactionID }); // 用映射工具(比如AutoMapper)复制变化的属性,或者手动赋值 // 示例:手动复制差异属性 trackedTransaction.Entity.accountNumber = updatedTransaction.accountNumber; trackedTransaction.Entity.TransDate = updatedTransaction.TransDate; trackedTransaction.Entity.Title = updatedTransaction.Title; // 其他字段同理... // EF会自动生成仅包含修改列的UPDATE语句 _dbContext.SaveChanges(); }
如果用AutoMapper,可以更简洁地处理属性映射:
// 提前配置AutoMapper:只映射非空/有变化的属性 _mapper.Map(updatedTransaction, trackedTransaction.Entity);
优缺点:
- ✅ 代码简洁,无需手动写SQL,自动处理变更检测
- ✅ 无需维护字段对比逻辑,新增字段时自动适配
- ❌ 依赖EF Core框架,若用原生ADO.NET则不适用
方案3:反射自动对比字段(多字段场景优化)
如果Transaction字段很多,手动写对比代码太繁琐,可以用反射遍历属性,自动收集差异列,减少重复代码:
public void UpdateTransactionWithReflection(Transaction updatedTransaction) { var existingTransaction = _dbContext.Transactions .FirstOrDefault(t => t.TransactionID == updatedTransaction.TransactionID); if (existingTransaction == null) { throw new ArgumentException("目标交易记录不存在"); } // 获取Transaction的所有公共实例属性(排除主键TransactionID) var properties = typeof(Transaction).GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(p => p.Name != nameof(Transaction.TransactionID)); var updateClauses = new List<string>(); var sqlParameters = new List<SqlParameter>(); foreach (var prop in properties) { var existingValue = prop.GetValue(existingTransaction); var updatedValue = prop.GetValue(updatedTransaction); // 处理值类型、引用类型和null的对比 if (!Equals(existingValue, updatedValue)) { updateClauses.Add($"{prop.Name} = @{prop.Name}"); sqlParameters.Add(new SqlParameter($"@{prop.Name}", updatedValue ?? DBNull.Value)); } } if (updateClauses.Count > 0) { var updateSql = $"UPDATE Transactions SET {string.Join(", ", updateClauses)} WHERE TransactionID = @TransactionID"; sqlParameters.Add(new SqlParameter("@TransactionID", updatedTransaction.TransactionID)); _dbContext.Database.ExecuteSqlRaw(updateSql, sqlParameters.ToArray()); } }
额外提示:
- 注意修正代码中
catagory的拼写为标准的category,避免后续维护坑 - 对于DateTime类型,建议对比时截断毫秒(比如
existingTransaction.TransDate.Truncate(TimeSpan.FromMilliseconds(1))),避免数据库与.NET的精度差异导致误更新 - 反射可以缓存属性信息,减少重复反射的性能开销
内容的提问来源于stack exchange,提问作者John Evans
相关产品推荐
相关产品推荐

