You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Entity Framework .NET Core未抛出DbUpdateConcurrencyException问题排查

问题描述

我有一个基于SQL Server数据库构建EF数据集的Windows Forms程序。加载表单显示StudentsTable的所有当前值后,立即使用SSMS修改底层SQL Server表中某学生的GPA,再通过表单修改该学生的GPA时,程序接受更改但未抛出DbUpdateConcurrencyException。

模型类定义

public partial class StudentsTable
{
    public int StudentId { get; set; }
    public string Fname { get; set; } = null!;
    public string Lname { get; set; } = null!;
    public decimal Gpa { get; set; }
    public int MajorId { get; set; }
    public byte[] Updated { get; set; } = null!;
    public virtual MajorTable Major { get; set; } = null!;
}

DbContext配置

public partial class StudentDbContext : DbContext
{
    public StudentDbContext()
    {
    }

    public StudentDbContext(DbContextOptions<StudentDbContext> options)
        : base(options)
    {
    }

    public virtual DbSet<MajorTable> MajorTables { get; set; }
    public virtual DbSet<StudentsTable> StudentsTables { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseSqlServer("xxxxxxxxxxxxx;Database=StudentDB;Trusted_Connection=True; TrustServerCertificate=True");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<MajorTable>(entity =>
        {
            entity.HasKey(e => e.MajorId);
            entity.ToTable("MajorTable");
            entity.Property(e => e.MajorId).HasColumnName("MajorID");
            entity.Property(e => e.Major).HasMaxLength(50);
        });

        modelBuilder.Entity<StudentsTable>(entity =>
        {
            entity.HasKey(e => e.StudentId);
            entity.ToTable("StudentsTable");
            entity.Property(e => e.StudentId).HasColumnName("StudentID");
            entity.Property(e => e.Fname).HasMaxLength(50);
            entity.Property(e => e.Gpa)
                .HasColumnType("numeric(18, 0)")
                .HasColumnName("GPA");
            entity.Property(e => e.Lname).HasMaxLength(50);
            entity.Property(e => e.MajorId).HasColumnName("MajorID");
            entity.Property(e => e.Updated)
                .IsRowVersion()
                .IsConcurrencyToken();

            entity.HasOne(d => d.Major).WithMany(p => p.StudentsTables)
                .HasForeignKey(d => d.MajorId)
                .OnDelete(DeleteBehavior.ClientSetNull)
                .HasConstraintName("FK_StudentsTable_MajorTable");
        });

        OnModelCreatingPartial(modelBuilder);
    }

    partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

数据库的时间戳列对应模型中的Updated属性,已标记为IsRowVersion和IsConcurrencyToken。

更新操作代码

int newGPA;  //Variable to hold textBox input

if (int.TryParse(textBox1.Text, out newGPA))
{
    var selected = dataGridView1.CurrentRow;
    int selectedStudentId = (int)selected.Cells["StudentId"].Value;
    var selectedEvent =
        (from eachStudent in myContext.StudentsTables
            where eachStudent.StudentId == selectedStudentId
            select eachStudent).Single();
    selectedEvent.Gpa = newGPA;   //Edit one object

    try
    {
        myContext.SaveChanges();        // Save Changes
    }
    catch (Exception)
    {
        throw;
    }
}

请问是否需要在C#代码或SQL Server数据库中进行其他设置,以触发并发异常?


解决方案

1. 绕过Change Tracker获取最新实体

你的myContext大概率是表单加载时创建的长期实例,EF的Change Tracker会缓存第一次加载的实体(包括旧的Updated行版本值)。后续查询时,EF会直接返回缓存实例,不会从数据库拉取最新的行版本,导致并发检测失效。

解决方式:

  • 查询时使用AsNoTracking()绕过缓存:
var selectedEvent = myContext.StudentsTables
    .AsNoTracking()
    .Single(e => e.StudentId == selectedStudentId);
  • 或者在修改前强制刷新实体:
myContext.Entry(selectedEvent).Reload();

2. 确认数据库行版本列类型

确保SQL Server中Updated列是rowversion类型(而非普通varbinary),定义如下:

Updated rowversion NOT NULL

该类型会在每次行更新时自动生成新值,是EF检测并发的核心依据。

3. 优化DbContext生命周期

避免长期持有DbContext实例,建议在每次操作时创建短期实例并释放:

using (var myContext = new StudentDbContext())
{
    var selectedEvent = myContext.StudentsTables
        .Single(e => e.StudentId == selectedStudentId);
    selectedEvent.Gpa = newGPA;
    myContext.SaveChanges();
}

这样每次操作都会从数据库获取最新的行版本,自然能检测到并发修改。

4. 明确捕获并发异常

调整异常捕获逻辑,直接监听DbUpdateConcurrencyException,避免上层代码吞掉异常:

try
{
    myContext.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
    MessageBox.Show("该学生信息已被修改,请刷新后重试");
    // 可选:刷新实体到最新状态
    myContext.Entry(selectedEvent).Reload();
}
catch (Exception ex)
{
    // 处理其他异常
}

内容的提问来源于stack exchange,提问作者kurtfriedrich

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 15:23:19