使用EPPlus导入Excel更新数据库已有行时新增重复行如何解决
问题根因
导入时出现重复新增、不执行更新,是代码存在以下逻辑错误:
- Excel字段映射写错:重复给
gender字段赋值两次(分别取第5、第6列),实体的year字段完全没有从Excel读取,所有导入对象的year都是int默认值0,直接导致后续年份比对逻辑失效 - EF更新用法错误:传给
UpdateRange的是手动new的游离对象,没有数据库主键id,EF无法关联到已存在的数据库行,执行时会默认当作新数据插入 - 查询逻辑冗余低效:两次执行
Count()往返数据库做判断,性能差还容易出现数据不一致 - 无效校验:
ModelState.IsValid只校验请求表单绑定的参数,手动从Excel解析的实体不会走这个校验,这个判断完全不生效 - 缺少空值防护:Excel单元格为空时直接调用
ToString()会触发空引用异常
修复后代码
public class Insured { [Key] public int id { get; set; } [Required] public string identifier { get; set; } public string policyno { get; set; } public string firstname { get; set; } public string lastname { get; set; } public string gender { get; set; } public int year { get; set; } } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> ImportExcelFile(IFormFile ExcelFile) { ViewBag.Message = ""; if (ExcelFile == null || ExcelFile.Length == 0) { ViewBag.Message = "请上传有效的Excel文件"; return View(); } var importList = new List<Insured>(); using (var stream = new MemoryStream()) { await ExcelFile.CopyToAsync(stream); using (var package = new ExcelPackage(stream)) { // 判空工作表,避免找不到Sheet1抛错 var worksheet = package.Workbook.Worksheets["Sheet1"]; if (worksheet?.Dimension == null) { ViewBag.Message = "Excel文件内容为空或格式不符合要求"; return View(); } var rowCount = worksheet.Dimension.Rows; for (int row = 2; row <= rowCount; row++) { // 空值防护,处理单元格为空的场景 var identifier = worksheet.Cells[row, 1].Value?.ToString().Trim().ToLower(); var policyno = worksheet.Cells[row, 2].Value?.ToString().Trim(); // 跳过必填项为空的无效行 if (string.IsNullOrWhiteSpace(identifier) || string.IsNullOrWhiteSpace(policyno)) continue; var insured = new Insured { identifier = identifier, policyno = policyno, firstname = worksheet.Cells[row, 3].Value?.ToString().Trim() ?? string.Empty, lastname = worksheet.Cells[row, 4].Value?.ToString().Trim() ?? string.Empty, gender = worksheet.Cells[row, 5].Value?.ToString().Trim() ?? string.Empty, // 修正原代码错误:第6列对应year字段,增加类型转换容错 year = int.TryParse(worksheet.Cells[row, 6].Value?.ToString().Trim(), out var y) ? y : 0 }; importList.Add(insured); } } } // 批量处理导入数据 foreach (var item in importList) { // 单次查询取出匹配的已存在记录,替代两次Count冗余查询 var existRecord = _db.dbLifeData.FirstOrDefault(o => o.identifier == item.identifier && o.policyno == item.policyno); if (existRecord != null) { // 匹配到已有记录,按业务规则判断年份满足才更新 if (existRecord.year <= item.year) { // 直接修改EF跟踪的已有实体属性,不要传new的游离对象给Update existRecord.firstname = item.firstname; existRecord.lastname = item.lastname; existRecord.gender = item.gender; existRecord.year = item.year; // 实体已被上下文跟踪,不需要显式调用Update,改值后SaveChanges自动生成Update语句 } } else { // 不存在匹配记录才新增 _db.dbLifeData.Add(item); } } // 循环外统一提交,减少数据库连接开销,EF默认包裹事务,保证数据一致性 await _db.SaveChangesAsync(); ViewBag.Message = $"导入完成,共处理{importList.Count}条数据"; return View(); }
关键调整说明
- 修正Excel列和实体字段的映射错误,补全
year字段的读取逻辑,增加空值和类型转换容错处理 - 废弃冗余的两次Count查询,单次数据库查询直接取出匹配的已存在实体,性能更高
- 更新逻辑直接操作EF上下文跟踪的数据库已存在实体,EF可以正确识别变更,生成UPDATE语句而不是INSERT语句,从根源避免重复插入
- 移除无效的
ModelState.IsValid判断,增加必填字段非空校验,自动跳过无效行 - 将
SaveChanges移到循环外批量提交,减少数据库IO开销 - 补充文件、工作表为空的边界场景处理,避免运行时异常
内容的提问来源于stack exchange,提问作者Veck
相关产品推荐
相关产品推荐

