EF核心/ Sqlite一对多关系由于唯一索引约束而失败

Jan*_* Go 3 c# sql sqlite entity-framework 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; }
    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");
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的代码的示例执行...

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是当我被添加到数据库时,出现“唯一约束失败:CarBrands.CarBrandId”异常。car2CarBrand honda

我期望它在第二次交易期间执行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...
        }
Run Code Online (Sandbox Code Playgroud)

Iva*_*oev 5

问题在于Add方法会级联:

开始跟踪给定的实体以及尚未被跟踪的其他任何可达实体,Added状态为在SaveChanges调用它们时将其插入数据库中。

有很多方法可以实现目标,但是最灵活的方法(我想是首选)是用Add方法替换方法调用ChangeTracker.TrackGraph

通过遍历其导航属性开始跟踪一个实体以及所有可到达的实体。遍历是递归的,因此任何发现的实体的导航属性也将被扫描。将为每个发现的实体调用指定的回调,并且必须设置应跟踪每个实体的状态。如果未设置状态,则该实体将保持未跟踪状态。 此方法设计用于断开连接的场景,在该场景中,使用上下文的一个实例检索实体,然后使用上下文的另一个实例保存更改。一个示例是Web服务,其中一个服务调用从数据库中检索实体,而另一个服务调用保留对实体的任何更改。每个服务调用都使用一个新的上下文实例,该实例在调用完成后就被释放。如果发现上下文已经跟踪的实体,则不会处理该实体(并且不会遍历其导航属性)。

因此,context.Cars.Add(car2);您可以使用以下方法(它非常通用,并且几乎可以在所有情况下使用):

context.ChangeTracker.TrackGraph(car2, node =>
    node.Entry.State = !node.Entry.IsKeySet ? EntityState.Added : EntityState.Unchanged);
Run Code Online (Sandbox Code Playgroud)


mwi*_*ski 4

快速解决:

ContextEntityFramework Core 在您的代码中使用 new内部使用另一代码时出现问题。您正在混合两者之间的实体状态。只需使用相同的上下文来获取和创建即可。如果你选择:

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
}
Run Code Online (Sandbox Code Playgroud)

应该没问题。


如果你真的想获得两个上下文

    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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

您现在不依赖于更改跟踪机制,而是依赖于基于CarBrandId.


原因:更改跟踪

错误可能会产生误导,但如果您仔细查看上下文StateManager,您就会明白原因。因为CarBrand您从另一个实例获取的Context是您正在使用的实例,所以Context它的另一个实例看起来像一个新实例。您可以在调试中使用该特定数据库上下文中该特定实体的属性EntityState清楚地看到它:

在此输入图像描述

它说 CarBrand 现在被添加到数据库中Id: 1,这就是 CarBrand 表的主键的唯一约束被打破。

错误表示它CarBrandId打破了限制,因为您通过由 . 支持的关系 Car-CarBrand 强制在数据库中创建新记录CarBrandId

Id: 1在您用来查询数据库的上下文中,CarBrand 的状态是什么?当然不变。但这是唯一Context知道的人:

在此输入图像描述

如果您想深入了解该主题,请Change Tracking在此处阅读 EF Core 中的概念: https: //learn.microsoft.com/en-us/ef/core/querying/tracking