簡體   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