使用事务更改 EF Core 中的唯一约束列

Changing unique constrained column in EF Core using a transaction

我正在使用 EF Core(在 ASP.NET Core 中,我的 DbContext 寿命为 request/scope)。

我的 Employee 实体有这个 属性:

public bool? IsBoss { get; set; }

和这个配置:

entityBuilder.Property(b => b.IsBoss).IsRequired(false);
entityBuilder.HasIndex(b => b.IsBoss).IsUnique();

这会创建一个过滤索引,因此只能有一个为真,一个为假,但有很多空值。

我的应用程序要求我总是只有一名员工 IsBoss==true

假设我想调换两个员工。

employee1.IsBoss = null;
employee2.IsBoss = true;
context.SaveChanges();

这会抛出一个违反唯一约束的异常。

我可以通过将其包装在交易中来解决这个问题:

using (var transaction = context.BeginTransaction())
{
  try
  {
    employee1.IsBoss = null;
    context.SaveChanges();

    employee2.IsBoss = true;
    context.SaveChanges();

    transaction.Commit();
  }
  catch
  {
    transaction.Rollback();
  }
}

我的问题是:为什么第一种方法会失败?我认为 EF Core automatically wraps 一切都在一个事务中。为什么我必须使用交易?

第一种方法由于与事务不同的原因而失败。 Tracking issue 在 EF 存储库上,它涵盖了与您相同的场景。

当调用 SaveChanges 时,EF 处理更改并计算要发送到数据库的命令。这组命令可以具有依赖性。与您的情况一样,您需要先将 employee1 的值设置为 null,然后才能将 employee2 的值设置为 true。 EF 对命令进行排序以找出它们需要执行的顺序。 EF 根据外键约束进行了这种排序。但正如该问题所报告的那样,我们没有考虑唯一索引,因此命令以错误的顺序发送,导致违反唯一约束。

该问题已在当前代码库中修复。它将在下一个 public 版本中提供。同时,作为一种变通方法,您需要像在第二个代码中那样调用 SaveChanges 两次。多次调用 SaveChanges 可以控制发送到数据库的命令的顺序。您不需要将其包装在一个事务中,除非您希望这两个更改都是一个原子操作。每个 SaveChanges 都有自己的事务,除非用户启动一个。