使用EntityFramework 6 Code-First播种期间的IDENTITY_INSERT

我有一个具有Auto-identity (int)列的实体。 作为数据种子的一部分,我想在我的系统中使用“标准数据”的特定标识符值,之后我想让数据库对id值进行排序。

到目前为止,我已经能够将IDENTITY_INSERT设置为On作为插入批处理的一部分,但entity framework不会生成包含Id的插入语句。 这是有道理的,因为模型认为数据库应该提供值,但在这种情况下,我想提供值。

模型(伪代码):

 public class ReferenceThing { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id{get;set;} public string Name{get;set;} } public class Seeder { public void Seed (DbContext context) { var myThing = new ReferenceThing { Id = 1, Name = "Thing with Id 1" }; context.Set.Add(myThing); context.Database.Connection.Open(); context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT ReferenceThing ON") context.SaveChanges(); // <-- generates SQL INSERT statement // but without Id column value context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT ReferenceThing OFF") } } 

任何人都能提供任何见解或建议吗?

所以我可能通过生成我自己的包含Id列的SQL插入语句来解决这个问题。 这感觉就像一个可怕的黑客,但它的工作原理: – /

 public class Seeder { public void Seed (DbContext context) { var myThing = new ReferenceThing { Id = 1, Name = "Thing with Id 1" }; context.Set.Add(myThing); context.Database.Connection.Open(); context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT ReferenceThing ON") // manually generate SQL & execute context.Database.ExecuteSqlCommand("INSERT ReferenceThing (Id, Name) " + "VALUES (@0, @1)", myThing.Id, myThing.Name); context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT ReferenceThing OFF") } } 

我为我的DbContext创建了一个替代构造函数,它接受一个bool allowIdentityInserts 。 我将该bool设置为DbContext上的同名私有字段。

如果我在“模式”中创建上下文,我的OnModelCreating然后“ OnModelCreating指定”身份规范

  protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove(); if(allowIdentityInsert) { modelBuilder.Entity() .Property(x => x.Id) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); } } 

这允许我插入Ids而不更改我的实际数据库标识规范。 我仍然需要使用你所做的身份插入开/关技巧,但至少EF会发送Id值。

如果使用数据库优先模型,则应将ID列的StoreGeneratedPattern属性从Identity更改为None

在那之后,正如我在这里回答的那样,这应该有所帮助:

 using (var transaction = context.Database.BeginTransaction()) { var myThing = new ReferenceThing { Id = 1, Name = "Thing with Id 1" }; context.Set.Add(myThing); context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT ReferenceThing ON"); context.SaveChanges(); context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT ReferenceThing OFF"); transaction.Commit(); } 

没有第二个EF级别模型就无法完成 – 复制种子的类。

正如您所说 – 您的元数据表明数据库提供的值是播种期间不提供的值。

在尝试了本网站上的几个选项后,以下代码适用于我( EF 6 )。 请注意,如果该项目已存在,它将首先尝试正常更新。 如果没有,则尝试正常插入,如果错误是由IDENTITY_INSERT引起的,则尝试解决方法。 另请注意,db.SaveChanges将失败,因此db.Database.Connection.Open()语句和可选的validation步骤。 请注意,这不是更新上下文,但在我的情况下,没有必要。 希望这可以帮助!

 public static bool UpdateLeadTime(int ltId, int ltDays) { try { using (var db = new LeadTimeContext()) { var result = db.LeadTimes.SingleOrDefault(l => l.LeadTimeId == ltId); if (result != null) { result.LeadTimeDays = ltDays; db.SaveChanges(); logger.Info("Updated ltId: {0} with ltDays: {1}.", ltId, ltDays); } else { LeadTime leadtime = new LeadTime(); leadtime.LeadTimeId = ltId; leadtime.LeadTimeDays = ltDays; try { db.LeadTimes.Add(leadtime); db.SaveChanges(); logger.Info("Inserted ltId: {0} with ltDays: {1}.", ltId, ltDays); } catch (Exception ex) { logger.Warn("Error captured in UpdateLeadTime({0},{1}) was caught: {2}.", ltId, ltDays, ex.Message); logger.Warn("Inner exception message: {0}", ex.InnerException.InnerException.Message); if (ex.InnerException.InnerException.Message.Contains("IDENTITY_INSERT")) { logger.Warn("Attempting workaround..."); try { db.Database.Connection.Open(); // required to update database without db.SaveChanges() db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT[dbo].[LeadTime] ON"); db.Database.ExecuteSqlCommand( String.Format("INSERT INTO[dbo].[LeadTime]([LeadTimeId],[LeadTimeDays]) VALUES({0},{1})", ltId, ltDays) ); db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT[dbo].[LeadTime] OFF"); logger.Info("Inserted ltId: {0} with ltDays: {1}.", ltId, ltDays); // No need to save changes, the database has been updated. //db.SaveChanges(); <-- causes error } catch (Exception ex1) { logger.Warn("Error captured in UpdateLeadTime({0},{1}) was caught: {2}.", ltId, ltDays, ex1.Message); logger.Warn("Inner exception message: {0}", ex1.InnerException.InnerException.Message); } finally { db.Database.Connection.Close(); //Verification if (ReadLeadTime(ltId) == ltDays) { logger.Info("Insertion verified. Workaround succeeded."); } else { logger.Info("Error!: Insert not verified. Workaround failed."); } } } } } } } catch (Exception ex) { logger.Warn("Error in UpdateLeadTime({0},{1}) was caught: {2}.", ltId.ToString(), ltDays.ToString(), ex.Message); logger.Warn("Inner exception message: {0}", ex.InnerException.InnerException.Message); Console.WriteLine(ex.Message); return false; } return true; } 

尝试将此代码添加到您的数据库上下文“以保持其清洁”,以便说:

用法场景示例(将ID 0默认记录添加到实体类型ABCStatus:

 protected override void Seed(DBContextIMD context) { bool HasDefaultRecord; HasDefaultRecord = false; DBContext.ABCStatusList.Where(DBEntity => DBEntity.ID == 0).ToList().ForEach(DBEntity => { DBEntity.ABCStatusCode = @"Default"; HasDefaultRecord = true; }); if (HasDefaultRecord) { DBContext.SaveChanges(); } else { using (var dbContextTransaction = DBContext.Database.BeginTransaction()) { try { DBContext.IdentityInsert(true); DBContext.ABCStatusList.Add(new ABCStatus() { ID = 0, ABCStatusCode = @"Default" }); DBContext.SaveChanges(); DBContext.IdentityInsert(false); dbContextTransaction.Commit(); } catch (Exception ex) { // Log Exception using whatever framework Debug.WriteLine(@"Insert default record for ABCStatus failed"); Debug.WriteLine(ex.ToString()); dbContextTransaction.Rollback(); DBContext.RollBack(); } } } } 

为Get Table Name扩展方法添加此帮助程序类

 public static class ContextExtensions { public static string GetTableName(this DbContext context) where T : class { ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext; return objectContext.GetTableName(); } public static string GetTableName(this ObjectContext context) where T : class { string sql = context.CreateObjectSet().ToTraceString(); Regex regex = new Regex(@"FROM\s+(?.+)\s+AS"); Match match = regex.Match(sql); string table = match.Groups["table"].Value; return table; } }

要添加到DBContext的代码:

 public MyDBContext(bool _EnableIdentityInsert) : base("name=ConnectionString") { EnableIdentityInsert = _EnableIdentityInsert; } private bool EnableIdentityInsert = false; protected override void OnModelCreating(DbModelBuilder modelBuilder) { Database.SetInitializer(new MigrateDatabaseToLatestVersion()); //modelBuilder.Entity() // .Property(e => e.SomeProperty) // .IsUnicode(false); // Etc... Configure your model // Then add the following bit if (EnableIdentityInsert) { modelBuilder.Entity() .Property(x => x.ID) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); modelBuilder.Entity() .Property(x => x.ID) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); } } //Add this for Identity Insert ///  /// Enable Identity insert for specified entity type. /// Note you should wrap the identity insert on, the insert and the identity insert off in a transaction ///  /// Entity Type /// If true sets identity insert on else set identity insert off public void IdentityInsert(bool On) where T: class { if (!EnableIdentityInsert) { throw new NotSupportedException(string.Concat(@"Cannot Enable entity insert on ", typeof(T).FullName, @" when _EnableIdentityInsert Parameter is not enabled in constructor")); } if (On) { Database.ExecuteSqlCommand(string.Concat(@"SET IDENTITY_INSERT ", this.GetTableName(), @" ON")); } else { Database.ExecuteSqlCommand(string.Concat(@"SET IDENTITY_INSERT ", this.GetTableName(), @" OFF")); } } //Add this for Rollback changes ///  /// Rolls back pending changes in all changed entities within the DB Context ///  public void RollBack() { var changedEntries = ChangeTracker.Entries() .Where(x => x.State != EntityState.Unchanged).ToList(); foreach (var entry in changedEntries) { switch (entry.State) { case EntityState.Modified: entry.CurrentValues.SetValues(entry.OriginalValues); entry.State = EntityState.Unchanged; break; case EntityState.Added: entry.State = EntityState.Detached; break; case EntityState.Deleted: entry.State = EntityState.Unchanged; break; } } } 

根据上一个问题,您需要开始上下文的交易。 保存更改后,您还必须重新标识“身份插入”列,最后必须提交事务。

 using (var transaction = context.Database.BeginTransaction()) { var item = new ReferenceThing{Id = 418, Name = "Abrahadabra" }; context.IdentityItems.Add(item); context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;"); context.SaveChanges(); context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT [dbo].[User] OFF"); transaction.Commit(); } 

对于未来的Google员工,我发现答案表明OnModelCreating()中的一些条件逻辑对我不起作用。

这种方法的主要问题是EF缓存模型,因此无法在同一应用程序域中打开或关闭身份。

我们采用的解决方案是创建第二个派生的DbContext ,允许插入标识。 这样,两个模型都可以缓存,并且当您需要插入标识值时,可以在特殊(并且希望)罕见的情况下使用派生的DbContext

鉴于以下来自@ RikRak的问题:

 public class ReferenceThing { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public string Name { get; set; } } public class MyDbContext : DbContext { public DbSet ReferenceThing { get; set; } } 

我们添加了这个派生的DbContext

 public class MyDbContextWhichAllowsIdentityInsert : MyDbContext { protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity() .Property(x => x.Id) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); } } 

然后将与Seeder一起使用,如下所示:

 var specialDbContext = new MyDbContextWhichAllowsIdentityInsert(); Seeder.Seed(specialDbContext);