EF Core 6中忽略并发冲突批量更新Wallet表TournamentId为Null的最佳实践
最佳实践方案与代码修正
现有代码的核心问题
- 无过滤逻辑:原SQL会将所有
Wallet的TournamentId置空,完全不符合"仅处理已结束锦标赛关联钱包"的需求 - 错误的SQL执行方式:
FromSqlRaw用于查询数据,不会执行更新操作,SaveChangesAsync也不会提交任何变更 - 无效的并发处理:循环重试逻辑完全多余,因为根本没有跟踪实体变更,不会触发
DbUpdateConcurrencyException
方案1:原生SQL批量更新(性能最优)
直接通过数据库执行批量更新,无需加载实体,适合大量数据场景,天然忽略其他字段的并发变更:
private async Task CloseTournamentWallets(int tournamentId) { // 可选:先校验锦标赛是否已结束(根据业务逻辑调整) var tournament = await _context.Tournaments.FindAsync(tournamentId); if (tournament == null || tournament.EndDate > DateTime.UtcNow || tournament.IsActive) { return; } // 仅更新指定锦标赛关联的钱包 var affectedRows = await _context.Database.ExecuteSqlRawAsync( "UPDATE Wallets SET TournamentId = NULL WHERE TournamentId = @TournamentId", new SqlParameter("@TournamentId", tournamentId)); // 可选:记录更新行数用于日志 // _logger.LogInformation("更新了 {Count} 个钱包的锦标赛关联,锦标赛ID:{Id}", affectedRows, tournamentId); }
优势:
- 性能极高,直接在数据库层操作,避免内存加载大量实体
- 天然忽略并发:仅修改
TournamentId字段,不依赖Total/Available的原始值,其他事务修改这两个字段不影响更新
方案2:EF Core原生批量更新(强类型安全)
使用EF Core 6.x自带的ExecuteUpdateAsync API,无需手写SQL,编译时强类型检查:
private async Task CloseTournamentWallets(int tournamentId) { // 可选:校验锦标赛状态 var tournament = await _context.Tournaments.FindAsync(tournamentId); if (tournament == null || tournament.EndDate > DateTime.UtcNow || tournament.IsActive) { return; } // EF Core原生批量更新语法 var affectedRows = await _context.Wallets .Where(w => w.TournamentId == tournamentId) .ExecuteUpdateAsync(s => s.SetProperty(w => w.TournamentId, null)); // 可选:日志记录 // _logger.LogInformation("更新了 {Count} 个钱包的锦标赛关联,锦标赛ID:{Id}", affectedRows, tournamentId); }
优势:
- 强类型编译校验,避免SQL拼写错误
- 与EF Core上下文集成,自动处理参数化查询,防止SQL注入
- 性能与原生SQL接近,同样忽略其他字段的并发变更
并发冲突处理说明
需求要求忽略Total/Available字段的并发变更,上述两个方案均满足:
- 两种方案都是直接修改
TournamentId字段,数据库UPDATE操作会直接覆盖该值,不受其他字段变更影响 - 无需捕获
DbUpdateConcurrencyException,因为没有加载实体并依赖其原始值进行修改,不会触发乐观并发异常
内容的提问来源于stack exchange,提问作者B Dimitrov
相关产品推荐
相关产品推荐

