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

时间:2017-04-19 15:45:21

标签: c# sql entity-framework sqlite 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");
    }
}

以下是我的代码执行示例...

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...
        }

2 个答案:

答案 0 :(得分:3)

问题在于Add方法级联:

  

开始在KeepWebSiteAliveJob.WebSiteRootUrl状态下跟踪给定实体以及尚未被跟踪的任何其他可访问实体,以便在{{1}时将它们插入到数据库中被称为。

实现目标的方法有很多种,但最灵活的(我认为首选)是使用ChangeTracker.TrackGraph方法替换Added方法调用:

  

开始通过遍历导航属性来跟踪实体和可访问的任何实体。遍历是递归的,因此还将扫描任何已发现实体的导航属性。为每个发现的实体调用指定的回调,并且必须设置应跟踪每个实体的State。如果未设置任何状态,则实体保持未跟踪状态。   此方法设计用于断开连接的场景,其中使用上下文的一个实例检索实体,然后使用不同的上下文实例保存更改。此示例是一个Web服务,其中一个服务call从数据库中检索实体,另一个服务调用将持久保存对实体的任何更改。每个服务调用都使用在调用完成时处置的上下文的新实例。   如果发现已经由上下文跟踪的实体,则不处理该实体(并且不遍历它的导航属性)。

所以代替SaveChanges你可以使用以下内容(它非常通用,几乎适用于所有场景):

Add

答案 1 :(得分:2)

的QuickFix:

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,您会看到原因。 因为您从另一个CarBrand获取的Context是您正在使用的Context,因此对于Id: 1的另一个实例,它看起来像是一个新实例。 您可以在调试中使用此特定数据库上下文中该特定实体的属性 EntityState 清楚地看到它:

enter image description here

它说CarBrand现在已添加CarBrandId的数据库,这是CarBrand表的PRIMARY KEY的UNIQUE约束被破坏。

错误说它的CarBrandId打破了约束,因为你强制通过Id: 1支持的Car-CarBrand关系在数据库中创建新记录。

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

enter image description here

如果您想深入了解该主题,请在此处阅读EF Core中 WITH categorystructure (id, parentid, parentname, NAME, depth, visible) AS (SELECT C.id, C.parentid, Cast('' AS NVARCHAR(500)) AS ParentName, C.NAME, 0 AS Depth, C.visible FROM category C WHERE id = @CategoryId UNION ALL SELECT ParentCategory.id, ParentCategory.parentid, categorystructure.NAME AS ParentName, ParentCategory.NAME, categorystructure.depth + 1 AS Depth, ParentCategory.visible FROM category ParentCategory INNER JOIN categorystructure ON ParentCategory.id = categorystructure.parentid) SELECT C.*, Isnull(SC.stockcount, 0) AS StockCount, CS.depth FROM categorystructure CS INNER JOIN category C ON C.id = CS.id LEFT JOIN (SELECT categoryid, Count(*) AS StockCount FROM stock_category SC INNER JOIN stock S ON S.id = SC.stockid WHERE S.published = 1 AND ( @AustPostShippingEnabled = 0 OR ( S.widthmm IS NOT NULL AND S.heightmm IS NOT NULL AND S.depthmm IS NOT NULL AND S.weightg IS NOT NULL ) ) GROUP BY categoryid) SC ON SC.categoryid = CS.id WHERE ( @IncludeSelf = 1 OR CS.id != @CategoryId ) GROUP BY C.id, C.posdissectionid, C.posfamilyclassid, C.parentid, C.code, C.NAME, C.description, C.azureid, C.extension, C.visible, C.orderindex, SC.stockcount, CS.depth HAVING Sum(CONVERT(INT, CS.visible)) = Count(*) 的概念:https://docs.microsoft.com/en-us/ef/core/querying/tracking