优化C# API中依赖父对象的多对象批量删除逻辑
优化学生关联成绩删除性能并保留日志记录的方案
核心优化思路
原来的逐条删除+日志记录会产生N次成绩删除请求 + N次日志插入请求(N为成绩数量),这是性能瓶颈的根源。优化方向是:
- 一次性获取所有待删除的成绩数据(1次DB查询)
- 批量生成并插入操作日志(可选:1次DB批量插入)
- 批量删除成绩记录(1次DB批量操作)
- 最后删除学生(1次DB操作)
全程通过事务保证数据一致性。
具体实现代码
1. 修改辅助函数,实现批量处理+日志记录
假设你使用的是EF Core 7+(支持ExecuteDelete),可以这样重构:
public async Task DeleteStudentGradeAsync(int studentId) { // 1. 一次性查询该学生所有成绩(仅查询日志需要的字段,减少内存开销) var gradesToDelete = await _dbContext.Grades .Where(g => g.StudentId == studentId) .Select(g => new { g.Id, g.Subject, g.Score, g.CreatedDate }) .ToListAsync(); if (!gradesToDelete.Any()) return; // 2. 批量生成日志记录(先收集到列表,再批量插入) var historyLogs = new List<HistoryGrade>(); foreach (var grade in gradesToDelete) { historyLogs.Add(new HistoryGrade { GradeId = grade.Id, OperationType = "Delete", Subject = grade.Subject, Score = grade.Score, OperationTime = DateTime.UtcNow // 补充其他日志所需字段 }); } // 批量插入日志(1次DB操作) _dbContext.HistoryGrades.AddRange(historyLogs); await _dbContext.SaveChangesAsync(); // 3. 批量删除成绩(1次DB操作,直接生成DELETE SQL,不加载完整实体) await _dbContext.Grades .Where(g => g.StudentId == studentId) .ExecuteDeleteAsync(); }
2. 整合到主删除函数并添加事务
确保整个删除流程在事务中执行,避免部分操作失败导致数据不一致:
public async Task DeleteStudentAsync(int studentId) { using var transaction = await _dbContext.Database.BeginTransactionAsync(); try { // 先处理成绩删除和日志记录 await DeleteStudentGradeAsync(studentId); // 删除学生 var student = await _dbContext.Students.FindAsync(studentId); if (student != null) { _dbContext.Students.Remove(student); await _dbContext.SaveChangesAsync(); } await transaction.CommitAsync(); } catch (Exception) { await transaction.RollbackAsync(); throw; // 抛出异常交由上层处理 } }
额外适配与优化
- 如果你的EF Core版本低于7,无法使用
ExecuteDelete,可以用以下方式批量删除(性能略逊但远优于逐条删除):_dbContext.Grades.RemoveRange(_dbContext.Grades.Where(g => g.StudentId == studentId)); await _dbContext.SaveChangesAsync(); - 若使用EF Core 8+,日志插入也可以用
ExecuteInsert进一步优化,直接生成批量INSERT SQL:await _dbContext.HistoryGrades .ExecuteInsertAsync( from g in gradesToDelete select new HistoryGrade { GradeId = g.Id, OperationType = "Delete", Subject = g.Subject, Score = g.Score, OperationTime = DateTime.UtcNow });
性能对比
原来10条成绩需要20次DB调用(10次删成绩+10次插日志),优化后最多仅需4次DB调用(1次查成绩+1次插日志+1次删成绩+1次删学生),性能提升显著。
内容的提问来源于stack exchange,提问作者Marin Puscasu
相关产品推荐
相关产品推荐

