实体框架在两个不同的上下文中两次插入实体

时间:2018-12-12 18:30:18

标签: c# entity-framework entity-framework-core

我正在使用Entity Framework Core将对象图存储在数据库中。在构建图的各个时间,我都会创建一个实体,将其存储到数据库中,然后释放上下文。但是,我遇到了一个问题,其中EFC尝试插入一个在连接到新实体时已经插入的实体。最好用代码解释。这是一段简短的repro代码(这是直线代码,但是上下文的两种用法发生在代码中的不同时间和位置)。

在第二次致电context.SaveChanges()时,出现以下异常:

SqlException:

  

当IDENTITY_INSERT设置为OFF时,无法在表“名称空间”中为标识列插入显式值。

当我查看正在执行的SQL时,它正在尝试再次插入名称空间实体,大概是因为myType已保存到数据库,并且它引用了dbNamespace实体。

// see if namespace is in the db and add it if not
string someNamespaceString = "foobar";
CodeDatabase.Models.Namespace dbNamespace;
using (var context = new CodeFactsContext())
{
    dbNamespace = context.Namespace.FirstOrDefault(ns => ns.Namespace1 == someNamespaceString);
    if (dbNamespace == null)
    {
        dbNamespace = new Namespace() { Namespace1 = someNamespaceString };
        context.Namespace.Add(dbNamespace);
    }
    context.SaveChanges();
}

// Type entity created somewhere from the code
var myType = new CodeDatabase.Models.Type()
{
    FullName = "foobar.mytype",
    ShortName = "mytype",
    Namespace = dbNamespace // this is already in the DB
};

// check if myType is in the db and add it if not
using (var context = new CodeFactsContext())
{
    var dbType = context.Type.FirstOrDefault(t => t.FullName == myType.FullName);
    if (dbType == null)
    {
        dbType = myType;
        context.Add(dbType);
    }
    context.SaveChanges(); // throws exception
}

有什么主意如何让EF Core识别(在第二个context.SaveChanges()中)myType应该插入数据库,但是myType.Namespace不应该因为它已经存在?这两个实体都有一个由数据库自动生成的int id,并且在第一次调用SaveChanges之后,命名空间的id被设置为数据库值。我以为EF Core会认识到ID不为0,因此不尝试保存它。任何帮助/建议都非常欢迎。

1 个答案:

答案 0 :(得分:2)

  

我认为EFC会认识到ID不为0,因此不尝试保存它。

问题是您正在使用Add方法,该方法将所有可访问且未跟踪的实体都标记为新实体,而不管其键值如何(这是为了允许标识插入方案)。 Disconnected Entities - Working with graphs - All new/all existing entities中对此进行了说明。当您的场景陷入Mix of new and existing entities时。

  

有什么主意如何让EFC识别(在第二个context.SaveChanges中)myType应该插入数据库,但是myType.Namespace不应该因为它已经存在?这两个实体都有一个int ID,该ID由数据库自动生成,并且在第一次调用SaveChanges之后,命名空间的ID被设置为数据库值。

实际上,第二个文档链接中介绍了一个简单的解决方案:

  

使用自动生成的键,即使图形包含需要插入的实体和需要更新的实体的混合,更新也可以再次用于插入和更新

其中“再次”指Saving single entities

  

Update方法通常将实体标记为要更新,而不是插入。但是,如果实体具有自动生成的键,并且未设置键值,则该实体会自动标记为插入。

幸运的是,您的实体使用自动生成的密钥,因此只需使用Update而不是Add

if (dbType == null)
{
    dbType = myType;
    context.Update(dbType); // <--
}