唯一索引约束上的EF Core / Sqlite一对多关系失败
上下文是每个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 Cars { get; set; } public DbSet CarBrands { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite(@"Data Source = MyDatabase.sqlite"); } }
这是我的代码的示例执行…
class Program { static void Main(string[] args) { AlwaysCreateNewDatabase(); //1st transaction using (var context = new MyContext()) { var honda = new CarBrand() { Name = "Honda" }; var car1 = new Car() { CarBrand = honda }; context.Cars.Add(car1); context.SaveChanges(); } //2nd transaction using (var context = new MyContext()) { var honda = GetCarBrand(1); var car2 = new Car() { CarBrand = honda }; context.Cars.Add(car2); context.SaveChanges(); // exception happens here... } } static void AlwaysCreateNewDatabase() { using (var context = new MyContext()) { context.Database.EnsureDeleted(); context.Database.EnsureCreated(); } } static CarBrand GetCarBrand(int Id) { using (var context = new MyContext()) { return context.CarBrands.Find(Id); } } }
问题是当car2
被添加到具有相同CarBrand
honda
的数据库时,我得到‘UNIQUE约束失败:CarBrands.CarBrandId’exception。
我期望它做的是在第二个事务的context.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... }
问题是Add
方法级联:
开始跟踪给定实体以及尚未被跟踪的任何其他可到达实体处于已
Added
状态,以便在调用SaveChanges
时将它们插入到数据库中。
有很多方法可以实现这个目标,但最灵活的(我认为首选)是使用ChangeTracker.TrackGraph
方法替换Add
方法调用:
开始通过遍历它的导航属性来跟踪实体和任何可到达的实体。 遍历是递归的,因此也将扫描任何发现的实体的导航属性。 为每个发现的实体调用指定的回调,并且必须设置应跟踪每个实体的State。如果未设置任何状态,则实体保持未跟踪状态。 此方法设计用于断开连接的场景,其中使用上下文的一个实例检索实体,然后使用上下文的不同实例保存更改。 一个例子是Web服务,其中一个服务调用从数据库中检索实体,另一个服务调用持久保存对实体的任何更改。 每个服务调用都使用在调用完成时处置的上下文的新实例。 如果发现已经被上下文跟踪的实体,则不处理该实体(并且不遍历它的导航属性)。
所以代替context.Cars.Add(car2);
你可以使用以下(它非常通用,几乎适用于所有场景):
context.ChangeTracker.TrackGraph(car2, node => node.Entry.State = !node.Entry.IsKeySet ? EntityState.Added : EntityState.Unchanged);
快速解决:
EntityFramework Core使用另一个使用新的Context
,您的代码存在问题。 你正在混合两者之间的实体状态。 只需使用相同的上下文来获取和创建。 如果你选择:
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(); } }
您现在不依赖于更改跟踪机制,而是依赖于基于CarBrandId
简单FOREIGN KEY关系。
原因:更改跟踪
错误可能会产生误导,但如果您查看上下文的StateManager
,您会看到原因。 因为您从另一个Context
获取的CarBrand
是您正在使用的CarBrand
,对于Context
另一个实例,它看起来像一个新的。 您可以在调试中使用此特定数据库上下文中该特定实体的属性EntityState清楚地看到它:
它说CarBrand现在被添加到Id: 1
数据库,这是CarBrand表的PRIMARY KEY的UNIQUE约束被破坏。
错误说它的CarBrandId
打破了约束,因为你强制通过CarBrandId支持的Car-CarBrand关系在数据库中创建新记录。
在您用于查询数据库的上下文中,带有Id: 1
的CarBrand的状态是什么? 当然不变 。 但这是唯一了解它的Context
:
如果您想深入了解该主题,请在此处阅读EF Core中的Change Tracking
概念: https : //docs.microsoft.com/en-us/ef/core/querying/tracking
- 从C#中的进程读取环境变量
- C#Registry SetValue抛出UnauthorizedAccessException
- WebBrowser快捷方式无法在PowerPoint加载项中运行…但WebBrowserShortcutsEnabled为true
- 你能用JavaScript调用C#函数吗?
- “post的链接必须指向应用程序的连接或canvasURL”?
- 在客户端点击隐藏div
- 使用Twitter Bootstrap,C#,asp.net和javascript上传文件
- WP7中的UnhandledException
- 鼠标移开时关闭AJAX Control Toolkit BallonPopupExtender