繁体   English   中英

EF Core / Sqlite 一对多关系在唯一索引约束上失败

[英]EF Core / Sqlite one-to-many relationship failing on Unique Index Constraint

上下文是每个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");
    }
}

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

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);
        }
    }
}

问题是,我得到“UNIQUE约束失败:CarBrands.CarBrandId”时异常car2被添加到具有相同数据库CarBrand 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...
        }

问题是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打破约束,因为你通过由支持的关系轿车,CarBrand被迫在数据库中的新纪录创造CarBrandId

在用于查询数据库的上下文中,带有Id: 1的CarBrand的状态是什么? 当然不变 但这是唯一了解它的Context

在此输入图像描述

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

你不应该设置 CarBrand var car2 = new Car() { CarBrand = honda };

您应该设置CarBrandIdvar car2 = new Car() { CarBrandId = honda.CarBrandId };

你“可以”像这样构建模型:

            modelBuilder
                .Entity<Car>()
                .HasOne(c => c.CarBrand)
                .WithMany(cb => cb.Cars)
                .HasForeignKey(c => c.CarBrandId)
                .HasConstraintName("FK_Car_CarBrand")
                .IsRequired();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM