唯一索引約束上的EF Core / Sqlite一對多關係失敗

c# entity-framework entity-framework-core sql 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<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);
        }
    }
}

問題是當car2被添加到具有相同CarBrand honda的數據庫時,我得到'UNIQUE約束失敗:CarBrands.CarBrandId'異常。

我期望它做的是在第二個事務的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);


Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow