我正在使用EF Core(在ASP.NET Core中,我的DbContext
按照请求/范围生活。)
我的Employee
实体拥有此属性:
public bool? IsBoss { get; set; }
和这个配置:
entityBuilder.Property(b => b.IsBoss).IsRequired(false);
entityBuilder.HasIndex(b => b.IsBoss).IsUnique();
这会创建一个过滤索引,因此只能有一个true,一个false,但很多空。
我的应用要求我始终只有一名员工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在交易中的一切。为什么我必须使用交易?
答案 0 :(得分:1)
第一种方法由于与事务不同的原因而失败。 EF repo上的Tracking issue,涵盖了与您相同的方案。
调用SaveChanges
时,EF会处理更改并计算要发送到数据库的命令。这组命令可以具有依赖性。与您的情况类似,您需要为null
设置employee1
的值,然后才能为true
设置值employee2
。 EF对命令进行排序以找出需要执行的顺序。 EF基于外键约束进行了这种排序。但是,当该问题报告时,我们没有考虑唯一索引,因此命令以错误的顺序发送,导致唯一的约束违规。
此问题已在当前代码库中修复。它将在下一个公开发布中提供。同时作为解决方法,您需要在第二个代码中调用SaveChanges
两次。多次调用SaveChanges
可以控制发送到数据库的命令的顺序。除非您希望两个更改都是一个原子操作,否则不需要将它包装在事务中。除非用户启动,否则每个SaveChanges
都有自己的交易。