ASP.NET Core+EF中如何防止增改功能出现重复条目?
解决表单创建/编辑时重复字段的问题
要实现FirstName、LastName和DOB组合唯一的约束,并在重复时显示错误提示,需要从数据库约束和业务逻辑检查两方面入手,以下是具体方案:
1. 确保数据库唯一索引生效
之前的两种索引配置方式(特性或Fluent API)本身是正确的,但需要执行EF Core迁移才能将索引同步到数据库:
使用Fluent API配置索引(推荐)
在你的DbContext中保留这段代码:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<MRegForm>() .HasIndex(x => new { x.FirstName, x.LastName, x.DOB }) .IsUnique(); } public DbSet<MRegForm> MRegForms { get; set; } = default!;
执行迁移命令
打开包管理器控制台,运行以下命令更新数据库:
Add-Migration AddUniqueIndexForNameAndDOB Update-Database
这会在数据库中创建组合唯一索引,从底层阻止重复数据插入。
2. 在业务逻辑中添加重复检查与错误提示
仅靠数据库约束会抛出异常,需要在OnPostAsync中提前检查或捕获异常,将错误信息添加到模型状态,返回页面显示提示。
修改创建方法的OnPostAsync
public async Task<IActionResult> OnPostAsync() { MRegForm.CreatorId = UserManager.GetUserId(User); var isAuthorized = await AuthorizationService.AuthorizeAsync(User, MRegForm, RegFormOperations.Create); if (!isAuthorized.Succeeded) return Forbid(); // 检查是否存在重复的FirstName+LastName+DOB组合(忽略时间部分) var exists = await Context.MRegForms .AnyAsync(m => m.FirstName == MRegForm.FirstName && m.LastName == MRegForm.LastName && m.DOB.Date == MRegForm.DOB.Date); if (exists) { ModelState.AddModelError(string.Empty, "已存在相同姓名和出生日期的记录,请检查后重试。"); return Page(); } Context.MRegForms.Add(MRegForm); try { await Context.SaveChangesAsync(); } catch (DbUpdateException ex) { // 捕获并发场景下的数据库唯一约束异常(SQL Server错误码2601/2627) if (ex.InnerException is SqlException sqlEx && (sqlEx.Number == 2601 || sqlEx.Number == 2627)) { ModelState.AddModelError(string.Empty, "已存在相同姓名和出生日期的记录,请检查后重试。"); return Page(); } throw; } return RedirectToPage("./Index"); }
修改编辑方法的OnPostAsync
编辑时需要排除当前记录本身(编辑自身信息不算重复):
public async Task<IActionResult> OnPostAsync(int id) { var mRegForm = await Context.MRegForms.AsNoTracking().SingleOrDefaultAsync(m => m.MRegFormId == id); if (mRegForm == null) return NotFound(); MRegForm.CreatorId = mRegForm.CreatorId; MRegForm.Status = mRegForm.Status; var isAuthorized = await AuthorizationService.AuthorizeAsync(User, MRegForm, RegFormOperations.Update); if (!isAuthorized.Succeeded) return Forbid(); // 检查是否存在其他记录有相同的FirstName+LastName+DOB组合 var exists = await Context.MRegForms .AnyAsync(m => m.MRegFormId != id && m.FirstName == MRegForm.FirstName && m.LastName == MRegForm.LastName && m.DOB.Date == MRegForm.DOB.Date); if (exists) { ModelState.AddModelError(string.Empty, "已存在相同姓名和出生日期的记录,请检查后重试。"); return Page(); } Context.Attach(MRegForm).State = EntityState.Modified; try { await Context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!MRegFormExists(MRegForm.MRegFormId)) { return NotFound(); } else { throw; } } catch (DbUpdateException ex) { if (ex.InnerException is SqlException sqlEx && (sqlEx.Number == 2601 || sqlEx.Number == 2627)) { ModelState.AddModelError(string.Empty, "已存在相同姓名和出生日期的记录,请检查后重试。"); return Page(); } throw; } return RedirectToPage("./Index"); } private bool MRegFormExists(int id) { return Context.MRegForms?.Any(e => e.MRegFormId == id) ?? false; }
3. 在前端页面显示错误提示
在Create.cshtml和Edit.cshtml的表单上方添加错误显示区域:
@if (!ModelState.IsValid) { <div class="alert alert-danger"> @foreach (var error in ModelState.Values.SelectMany(v => v.Errors)) { <p>@error.ErrorMessage</p> } </div> }
关键说明
- 同时做业务检查和异常捕获是为了覆盖并发场景:可能在检查后、保存前有其他请求插入了重复数据,此时数据库约束会触发异常,需要捕获处理。
- 使用
DOB.Date匹配是为了忽略时分秒的差异,若需要精确到时间,可去掉.Date。 - 不同数据库的唯一约束错误码不同,MySQL为1062,需根据实际使用的数据库调整判断逻辑。
内容的提问来源于stack exchange,提问作者Morgan
相关产品推荐
相关产品推荐

