如何强制实体框架插入标识列?

Jez*_*Jez 54 database sql-server asp.net entity-framework ef-code-first

我想编写一些C#代码来使用一些种子数据初始化我的数据库.显然,这需要能够在插入时设置各种Identity列的值.我正在使用代码优先的方法.默认情况下,DbContext处理数据库连接,因此您不能SET IDENTITY_INSERT [dbo].[MyTable] ON.所以,到目前为止我所做的是使用DbContext构造函数,它允许我指定要使用的数据库连接.然后,我设置IDENTITY_INSERTON该数据库的连接,然后尝试使用实体框架插入我的记录.这是我到目前为止所得到的一个例子:

public class MyUserSeeder : IEntitySeeder {
    public void InitializeEntities(AssessmentSystemContext context, SqlConnection connection) {
        context.MyUsers.Add(new MyUser { MyUserId = 106, ConceptPersonId = 520476, Salutation = "Mrs", Firstname = "Novelette", Surname = "Aldred", Email = null, LoginId = "520476", Password="28c923d21b68fdf129b46de949b9f7e0d03f6ced8e9404066f4f3a75e115147489c9f68195c2128e320ca9018cd711df", IsEnabled = true, SpecialRequirements = null });
        try {
            connection.Open();
            SqlCommand cmd = new SqlCommand("SET IDENTITY_INSERT [dbo].[MyUser] ON", connection);
            int retVal = cmd.ExecuteNonQuery();
            context.SaveChanges();
        }
        finally {
            connection.Close();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如此接近但到目前为止 - 因为,虽然cmd.ExecuteNonQuery()工作正常,但是当我运行时context.SaveChanges(),我被告知"当IDENTITY_INSERT设置为ON或复制用户为时,必须为表'MyUser'中的标识列指定显式值插入NOT FOR REPLICATION标识列."

据推测,因为MyUserId(MyUser表中的Identity列)是主键,实体框架在我调用时不会尝试设置它context.SaveChanges(),即使我给了MyUser实体一个MyUserId属性值.

有没有办法强制实体框架尝试为实体插入偶数主键值,然后呢?或者也许是暂时标记MyUserId为不是主键值的方法,因此EF会尝试插入它?

Rom*_*n O 49

EF 6方法,使用msdn文章:

    using (var dataContext = new DataModelContainer())
    using (var transaction = dataContext.Database.BeginTransaction())
    {
      var user = new User()
      {
        ID = id,
        Name = "John"
      };

      dataContext.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[User] ON");

      dataContext.User.Add(user);
      dataContext.SaveChanges();

      dataContext.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[User] OFF");

      transaction.Commit();
    }
Run Code Online (Sandbox Code Playgroud)

更新:为避免错误"当IDENTITY_INSERT设置为ON或复制用户插入NOT FOR REPLICATION标识列时,必须为表'TableName'中的标识列指定显式值",您应该更改StoreGeneratedPattern属性的值模型设计器中从标识到无的标识列.

注意,将StoreGeneratedPattern更改为None将无法插入没有指定id的对象(正常方式),并且错误"当IDENTITY_INSERT设置为OFF时,无法在表'TableName'中为标识列插入显式值".

  • 当我尝试这个时,我得到了错误:"当IDENTITY_INSERT设置为ON或复制用户插入NOT FOR REPLICATION标识列时,必须为表'myTable'中的标识列指定显式值." (即使我在调用SaveChanges之前为我的表记录对象的标识字段设置了一个值.) (6认同)
  • 这根本不解决EF不插入密钥的问题 - 而是解决了如何使其成为事务性的问题 (3认同)
  • 似乎需要将它包装在事务中以使其工作 (3认同)

Rei*_*ica 18

你不需要做任何有趣的业务与连接,你可以削减中间人,只是使用ObjectContext.ExecuteStoreCommand.

然后你可以通过这样做来实现你想要的:

context.ExecuteStoreCommand("SET IDENTITY_INSERT [dbo].[MyUser] ON");
Run Code Online (Sandbox Code Playgroud)

我不知道有任何内置的方式告诉EF设置身份插入.

它并不完美,但它比你现在的方法更灵活,更少"hacky".

更新:

我刚刚意识到你的问题还有第二部分.既然你已经告诉SQL你想要进行身份插入,EF甚至都没有尝试为所述身份插入值(为什么会这样?我们还没告诉它).

我没有任何使用代码优先方法的经验,但是从一些快速搜索中,您似乎需要告诉EF您的列不应该从商店生成.你需要做这样的事情.

Property(obj => obj.MyUserId)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None)
    .HasColumnName("MyUserId");
Run Code Online (Sandbox Code Playgroud)

希望这会让你指出正确的方向:-)

  • 嗯,那个`Property`代码必须在'OnModelCreating`中完成,不是吗?在执行了"OnModelCreating"之后,没有办法向EF指示信息? (2认同)

Pet*_*ert 11

派对迟到了,但是如果有人在EF5中首先遇到这个问题:我无法获得任何解决方案,但找到了另一种解决方法:

在运行.SaveChanges()命令之前,我重置了表的身份计数器:

Entities.Database.ExecuteSqlCommand(String.Format("DBCC CHECKIDENT ([TableNameHere], RESEED, {0})", newObject.Id-1););
Entities.YourTable.Add(newObject);
Entities.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

这意味着.SaveChanges()每次添加后都需要应用 - 但至少它是有效的!

  • 节省时间的用户,修改列的解决方案并不理想,因为您需要两次更新生产服务器。这很完美。请记住,当您知道要插入第一条记录时,请删除“ -1”。 (2认同)
  • 如果插入代码可能可以同时运行(例如,典型网站的一部分),请确保包装SQL命令,并将行添加到事务中。否则,您的应用有时会认为新对象具有一个ID,而SQL Server将存储另一个ID。调试充满乐趣! (2认同)
  • 使用事务支持[内置于EF](https://msdn.microsoft.com/en-us/library/dn456843.aspx)或[EF Core](https://docs.microsoft.com/en-us/ EF /核心/省电/交易). (2认同)

Pav*_*vvy 6

这是问题的解决方案.我在EF6上尝试过,它对我有用.以下是一些应该有效的伪代码.

首先,您需要创建默认dbcontext的重载.如果检查基类,您将找到传递现有dbConnection的基类.检查以下代码 -

public MyDbContext(DbConnection existingConnection, bool contextOwnsConnection)
        : base(existingConnection, contextOwnsConnection = true)
    {
        //optional
        this.Configuration.ProxyCreationEnabled = true;
        this.Configuration.LazyLoadingEnabled = true;
        this.Database.CommandTimeout = 360;
    }
Run Code Online (Sandbox Code Playgroud)

并在On modelcreating中删除db生成的选项,如,

protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<MyTable>()
            .Property(a => a.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

        base.OnModelCreating(modelBuilder);
    }
Run Code Online (Sandbox Code Playgroud)

现在在代码中你需要显式传递连接对象,

using (var connection = new System.Data.SqlClient.SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionStringName"].ConnectionString))
        {
            connection.Open();
            using (var context = new MyDbContext(connection, true))
            {
                context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[MyTable] ON");
                context.MyTable.AddRange(objectList);
                context.SaveChanges();
                context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[MyTable] OFF");
            }

            connection.Close();
        }
Run Code Online (Sandbox Code Playgroud)


Jez*_*Jez 5

经过仔细考虑,我认为实体框架拒绝插入标识列是一个功能,而不是一个错误。:-) 如果我要在数据库中插入所有条目(包括它们的标识值),我还必须为实体框架自动为我创建的每个链接表创建一个实体!这不是正确的方法。

因此,我正在做的是设置仅使用 C# 代码的种子类并创建 EF 实体,然后使用 aDbContext保存新创建的数据。获取转储的 SQL 并将其转换为 C# 代码需要花费更长的时间,但是仅仅用于“播种”数据的数据不会(也不应该)过多 - 它应该是具有代表性的少量数据实时数据库中的数据类型可以快速放入新数据库中以进行调试/开发。这确实意味着,如果我想将实体链接在一起,我必须对已插入的内容进行查询,否则我的代码将不知道它们生成的标识值,例如。在我设置并完成以下操作后,这种事情将出现在种子代码context.SaveChangesMyRoles

var roleBasic = context.MyRoles.Where(rl => rl.Name == "Basic").First();
var roleAdmin = context.MyRoles.Where(rl => rl.Name == "Admin").First();
var roleContentAuthor = context.MyRoles.Where(rl => rl.Name == "ContentAuthor").First();

MyUser thisUser = context.MyUsers.Add(new MyUser {
    Salutation = "Mrs", Firstname = "Novelette", Surname = "Aldred", Email = null, LoginUsername = "naldred", Password="c1c966821b68fdf129c46de949b9f7e0d03f6cad8ea404066f4f3a75e11514748ac9f68695c2128e520ca0275cd711df", IsEnabled = true, SpecialRequirements = null
});
thisUser.Roles.Add(roleBasic);
Run Code Online (Sandbox Code Playgroud)

这样做也使得我更有可能在更改架构时更新我的​​播种数据,因为当我更改它时我可能会破坏播种代码(如果我删除一个字段或实体,则使用该字段的现有播种代码/entity 将无法编译)。如果使用 SQL 脚本进行种子设定,情况就不会如此,而且 SQL 脚本也不会与数据库无关。

所以我认为,如果您尝试设置实体的身份字段来执行数据库播种数据,那么您肯定采取了错误的方法。

如果我实际上将大量数据从 SQL Server 拖到 PostgreSQL(一个完整的实时数据库,而不仅仅是一些种子数据),我可以通过 EF 来完成,但我希望同时打开两个上下文,并编写一些代码以从源上下文中获取所有各种实体并将它们放入目标上下文中,然后保存更改。

一般来说,唯一适合插入标识值的时候是当您从一个数据库复制到同一 DBMS 中的另一个数据库(SQL Server -> SQL Server、PostgreSQL -> PostgreSQL 等)时,然后您就可以这样做它位于 SQL 脚本中,而不是 EF 代码优先(SQL 脚本不会与数据库无关,但也不需要如此;您不会在不同的 DBMS 之间切换)。

  • 这样做有一些有效的情况,例如从另一个系统导入数据。 (8认同)