更新实体时如何忽略报错记录,处理包含内部异常详情的错误
问题解决:实体更新时跳过错误记录仅保存合法数据
问题根因
你当前的实现逻辑是先将所有待写入的PAY实体全部添加到DbContext的跟踪集合,再统一调用SaveChanges()。EF默认会把本次所有写入操作封装到同一个事务中,任意一条数据触发数据库约束报错、实体校验失败,都会触发全局事务回滚,导致全部数据都无法入库。
最优实现方案(易上手、逻辑清晰)
采用逐行提交+错误隔离的逻辑:每次添加一条实体后就尝试提交,提交失败则将当前错误实体从Context的跟踪列表中移除,记录错误后继续处理下一条数据。如果数据量较大(超过1000条),可以调整为分批提交(如每20条提交一次,失败则拆分当前批次逐行校验),平衡性能与容错能力。
修改后的完整代码
private void btn_salary_Click(object sender, EventArgs e) { if (sg.HasRows(lib.tbl_pay) == true && chkShowOnly.Checked == false) { lib.ShowMessage(lib.tbl_pay + " has data can not import"); return; } sql = "SELECT * FROM PAY WHERE BankAc IS NOT null"; q1 = new DBFHelper(txt_olddir.Text).FillDataTable(sql); dataGridView1.DataSource = q1; if (chkShowOnly.Checked == true) return; DateTime xdate = sg.ServerDate; // 新增计数变量,分别统计成功、失败条数 int successCount = 0; int failCount = 0; for (int x = 0; x < q1.Rows.Count; x++) { PAY q11 = new PAY { BANKAC = sg.AsString(q1.Rows[x]["BankAc"]), MDATE = sg.AsDate(q1.Rows[x]["MDATE"].ToString()), BYEAR = sg.AsDate(q1.Rows[x]["MDATE"].ToString()).Year, BMONTH = sg.AsDate(q1.Rows[x]["MDATE"].ToString()).Month, PERPAY = q1.AsDecimal("PERPAY", x), BASIC = q1.AsDecimal("PERPAY", x), DARATE = q1.AsDecimal("DA", x), NEWDA = q1.AsDecimal("NEWDA",x), ADNDA = q1.AsDecimal ("ADNDA",x), HRA = q1.AsDecimal ("HRA",x), ENTREL = q1.AsDecimal("ENTREL",x), EXTRA = q1.AsDecimal("EXTRA",x), INSKNP = q1.AsDecimal("INSGKP",x), MEDI = q1.AsDecimal("MEDI",x), RD = q1.AsDecimal("RD",x), GPF = q1.AsDecimal("GPF",x), GPFLOAN= q1.AsDecimal("GPFLOAN",x), CPF = q1.AsDecimal("CPF",x), CPFLOAN = q1.AsDecimal("CPFLOAN",x), LICPRE = q1.AsDecimal("LICPRE",x), LICLOAN = q1.AsDecimal("LICLOAN",x), ITAX = q1.AsDecimal("ITAX",x), JSBLOAN = q1.AsDecimal("JSBLOAN",x), NSBLOAN = q1.AsDecimal("NSBLOAN",x), STEMP = q1.AsDecimal("STEMP",x), PROFUND = q1.AsDecimal("PROFUND",x), TYPE_ATX = q1.AsString("TYPE",x), GRADE = q1.AsString("GRADE",x), TFUND = q1.AsDecimal("TFUND",x), GFUND = q1.AsDecimal("GFUND",x), OBCLOAN = q1.AsDecimal("OBCLOAN",x), OTH1_LOAN = q1.AsDecimal("OTH1_LOAN",x), OTH2_LOAN = q1.AsDecimal("OTH2_LOAN",x), BSTGBANK = q1.AsDecimal("BSTGBANK",x), EARNING = q1.AsDecimal("EARNING",x), TOTDED = q1.AsDecimal("TOTDED",x), NETPAY = q1.AsDecimal("NETPAY",x), CITYALW = q1.AsDecimal("CITYALW",x), user_code = lib.user_code, modi_dt = xdate }; g1.PAYs.Add(q11); try { // 单条提交,成功则计数+1 g1.SaveChanges(); successCount++; lbl_counter.Text = $"{successCount} 条记录已插入,{failCount} 条插入失败"; Application.DoEvents(); } catch (Exception ex) { failCount++; // 关键:将错误实体从上下文跟踪中移除,避免影响后续其他数据提交 g1.Entry(q11).State = EntityState.Detached; // 拼接全量错误信息 string emsg = $"第{x+1}条记录插入失败:"; Exception innerEx = ex; while (innerEx != null) { emsg += innerEx.Message; innerEx = innerEx.InnerException; } // 可根据需求选择弹窗提示/写入日志,不需要单条弹窗可以注释下行,最后统一汇总提示 lib.ShowMessage(emsg); } } lib.ShowMessage($"处理完成:共{q1.Rows.Count}条数据,成功插入{successCount}条,失败{failCount}条"); }
优化建议
- 建议新增前置校验逻辑:在构造
PAY实体前就检查字段合法性,比如MDATE是否能正常转为日期、数值字段是否为合法数字、必填字段是否为空,提前筛除错误数据,效率远高于触发数据库报错后再回滚。 - 如果数据量超过1000条,建议改为分批提交:比如每20条提交一次,提交失败再将当前批次逐条校验提交,减少数据库交互次数提升性能。
内容的提问来源于stack exchange,提问作者hpsingh10
相关产品推荐
相关产品推荐

