C# EF Core/Sqlite一对多关系在唯一索引约束上失败
上下文是每个C# EF Core/Sqlite一对多关系在唯一索引约束上失败,c#,sql,entity-framework,sqlite,entity-framework-core,C#,Sql,Entity Framework,Sqlite,Entity Framework Core,上下文是每个Car都有一个相应的CarBrand。现在我的课程如下所示: public class Car { public int CarId { get; set; } public int CarBrandId { get; set; } public CarBrand CarBrand { get; set; } } public class CarBrand { public int CarBrandId { get; set; } publi
Car
都有一个相应的CarBrand
。现在我的课程如下所示:
public class Car
{
public int CarId { get; set; }
public int CarBrandId { get; set; }
public CarBrand CarBrand { get; set; }
}
public class CarBrand
{
public int CarBrandId { get; set; }
public string Name { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Car> Cars { get; set; }
public DbSet<CarBrand> CarBrands { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(@"Data Source = MyDatabase.sqlite");
}
}
问题是当使用相同的CarBrand
将car2
添加到数据库时,我得到'UNIQUE constraint failed:CarBrands.CarBrandId'异常
我希望它能在第二个事务的上下文中执行。SaveChanges()
,它将添加car2
,并适当地设置它与CarBrand
的关系,但我得到了一个异常
编辑:我真的需要在不同的上下文/事务中获取我的CarBrand实例
//really need to get CarBrand instance from different context/transaction
CarBrand hondaDb = null;
using (var context = new MyContext())
{
hondaDb = context.CarBrands.First(x => x.Name == "Honda");
}
//2nd transaction
using (var context = new MyContext())
{
var car2 = new Car() { CarBrand = hondaDb };
context.Cars.Add(car2);
context.SaveChanges(); // exception happens here...
}
快速修复:
EntityFramework Core在使用新的上下文时遇到问题,您的代码在使用另一个上下文时出现问题。你在两者之间混合实体的状态。
只需使用相同的上下文进行获取和创建。
如果你同意:
using (var context = new MyContext())
{
var honda = context.CarBrands.Find(1);
var car2 = new Car() { CarBrand = honda };
context.Cars.Add(car2);
context.SaveChanges(); //fine now
var cars = context.Cars.ToList(); //will get two
}
应该没问题
如果你真的想得到两个上下文
您现在不依赖于更改跟踪机制,而是依赖于基于CarBrandId
的普通外键关系
原因:变更跟踪
这个错误可能会产生误导,但如果您查看上下文的StateManager
,就会发现原因。
因为您从另一个Context
获取的CarBrand
就是您正在使用的,对于Context
的另一个实例,它看起来像一个新实例。
您可以在调试中清楚地看到它,在这个特定的DB上下文中,使用该特定实体的属性EntityState:
它说CarBrand现在被添加到Id为1
的数据库中,这是CarBrand表主键被破坏的唯一约束
错误表明其CarBrandId
打破了约束,因为您通过CarBrandId
支持的关系Car-CarBrand在数据库中强制创建了新记录
在您用来查询数据库的上下文中,Id为1的CarBrand的状态是什么当然没有改变。但这是唯一了解它的上下文
:
如果您想更深入地了解该主题,请阅读EF Core中的更改跟踪的概念:问题在于方法层叠:
开始跟踪给定实体、以及尚未被跟踪的任何其他可访问实体,处于添加的状态,以便在调用保存更改时将其插入数据库
实现目标的方法有很多,但最灵活的(我想也是首选的)方法是用以下方法替换Add
方法调用:
开始跟踪实体以及通过遍历其导航属性可访问的任何实体。遍历是递归的,因此任何发现的实体的导航属性也将被扫描。为每个发现的实体调用指定的回调,并且必须设置跟踪每个实体的状态。如果未设置状态,则实体将保持未跟踪状态。
此方法设计用于断开连接的场景,其中使用一个上下文实例检索实体,然后使用另一个上下文实例保存更改。此方法的一个示例是web服务,其中一个服务调用从数据库检索实体,另一个服务调用保留任何更改给实体。每个服务调用都使用一个新的上下文实例,该实例在调用完成时被释放。
如果发现已被上下文跟踪的实体,则不会处理该实体(并且不会遍历其导航属性)
因此,不要使用context.Cars.Add(car2)代码>您可以使用以下内容(这是非常通用的,几乎适用于所有场景):
不确定这是否是导致问题的原因,但我通常看不到CRUD上的静态操作。为什么每次都必须使用不同的上下文?假设我重新启动了应用程序。那么这将是在不同的上下文中,对吗?如果我真的需要在不同的上下文/事务中获取CarBrand实例,该怎么办?@paolog如果你真的想使用不同的上下文,你应该查看存储库模式和另一个添加项(我确实意识到,你可能正在尝试将比这里显示的更广泛的想法可视化)。
using (var context = new MyContext())
{
var honda = context.CarBrands.Find(1);
var car2 = new Car() { CarBrand = honda };
context.Cars.Add(car2);
context.SaveChanges(); //fine now
var cars = context.Cars.ToList(); //will get two
}
static void Main(string[] args)
{
AlwaysCreateNewDatabase();
CarBrand hondaDb = null;
using (var context = new MyContext())
{
hondaDb = context.CarBrands.Add(new CarBrand {Name = "Honda"}).Entity;
context.SaveChanges();
}
using (var context = new MyContext())
{
var car2 = new Car() { CarBrandId = hondaDb.CarBrandId };
context.Cars.Add(car2);
context.SaveChanges();
}
}
context.ChangeTracker.TrackGraph(car2, node =>
node.Entry.State = !node.Entry.IsKeySet ? EntityState.Added : EntityState.Unchanged);