如何打开.net核心中的identity-insert

时间:2016-11-30 18:54:33

标签: .net asp.net-mvc entity-framework

我在EF中创建了几个表并输入了一些种子数据,我用一个主键为几列提供了值。当我运行应用程序时,我收到错误消息:

无法在表格中插入标识列的显式值'人员'当IDENTITY_INSERT设置为OFF时。

如何开启它?我在这里阅读使用:

[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]

作为主键的属性上方。遗憾的是,我仍然收到相同的错误消息。请帮忙。

我向所有拥有主键的属性添加了[DatabaseGenerated(DatabaseGeneratedOption.None)]。当我运行迁移时,我可以看到标识列已被删除,但我仍然收到相同的错误消息。

当我进入SQL SEO时,我仍然可以在主键上看到标识列。我尝试刷新数据库。我究竟做错了什么?我唯一能做的就是进入属性并删除身份,但为什么我不能按照上面提到的方式去做?

9 个答案:

答案 0 :(得分:10)

在EF Core 1.1.2中,我使用它来处理事务。在我的"数据库初始化程序"将种子数据放入表中。我使用了this EF6 answer中的技术。这是代码示例:

using (var db = new AppDbContext())
using (var transaction = db.Database.BeginTransaction())
{
    var user = new User {Id = 123, Name = "Joe"};
    db.Users.Add(user);
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT MyDB.Users ON;");
    db.SaveChanges();
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT MyDB.Users OFF");
    transaction.Commit();
}

答案 1 :(得分:8)

Steve Nyholm的答案很好,但我会提供一些额外的解释和一些通用代码以及异常处理。

通常上下文负责交易,但在这种情况下需要手动处理。为什么呢?

数据库上下文将在BEGIN TRAN发布后生成SET IDENTITY_INSERT。这将使事务的插入失败since IDENTITY_INSERT seems to affect tables at session/transaction level

因此,所有内容都必须包含在单个事务中才能正常工作。

以下是一些在关键级别(与表级别相对)播种的有用代码:

Extensions.cs

[Pure]
public static bool Exists<T>(this DbSet<T> dbSet, params object[] keyValues) where T : class
{
    return dbSet.Find(keyValues) != null;
}

public static void AddIfNotExists<T>(this DbSet<T> dbSet, T entity, params object[] keyValues) where T: class
{
    if (!dbSet.Exists(keyValues))
        dbSet.Add(entity);
}

DbInitializer.cs

(假设模型类名与表名相同)

private static void ExecuteWithIdentityInsertRemoval<TModel>(AspCoreTestContext context, Action<AspCoreTestContext> act) where TModel: class
{
    using (var transaction = context.Database.BeginTransaction())
    {
        try
        {
            context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT " + typeof(TModel).Name + " ON;");
            context.SaveChanges();
            act(context);
            context.SaveChanges();
            transaction.Commit();
        }
        catch(Exception)
        {
            transaction.Rollback();
            throw;
        }
        finally
        {
            context.Database.ExecuteSqlCommand($"SET IDENTITY_INSERT " + typeof(TModel).Name + " OFF;");
            context.SaveChanges();
        }
    }
}

public static void Seed(AspCoreTestContext context)
{
    ExecuteWithIdentityInsertRemoval<TestModel>(context, ctx =>
    {
        ctx.TestModel.AddIfNotExists(new TestModel { TestModelId = 1, ModelCode = "Test model #1" }, 1);
        ctx.TestModel.AddIfNotExists(new TestModel { TestModelId = 2, ModelCode = "Test model #2" }, 2);
    });
}

答案 2 :(得分:6)

@Steve Nyholm的答案还可以,但是在.Net核心3中,ExecuteSqlCommand已过时,ExecuteSqlInterpolated替换了ExecuteSqlCommand:

using (var db = new AppDbContext())
using (var transaction = db.Database.BeginTransaction())
{
    var user = new User {Id = 123, Name = "Joe"};
    db.Users.Add(user);
    db.Database.ExecuteSqlInterpolated($"SET IDENTITY_INSERT MyDB.Users ON;");
    db.SaveChanges();
    db.Database.ExecuteSqlInterpolated($"SET IDENTITY_INSERT MyDB.Users OFF");
    transaction.Commit();
}

答案 3 :(得分:3)

另一种方法是明确打开连接,然后SET IDENTITY_INSERT <table> ON

var conn = context.Database.GetDbConnection();
if (conn.State != ConnectionState.Open)
    conn.Open();

 context.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Posts ON");

 var post = new WeblogPost()
 {                    
               Id= oldPost.Pk,  //   <!--- explicit value to Id field
               Title = oldPost.Title,
                ...
 };
 context.Posts.Add(post);    
 conn.Close();

显然,一旦在EF请求之前显式打开了连接,EF就不会自动关闭该连接,因此该设置将应用于同一连接上下文。

这就是史蒂夫对交易的回应与交易保持联系有效的原因相同。

  

注意:您不希望将连接放入using语句,如果您打算稍后在应用程序/请求中再次使用相同的上下文。连接必须存在,因此清除连接上下文的最佳方法是.Close(),从而将EF返回到每个操作打开和关闭连接的默认行为。

答案 4 :(得分:2)

必须处理相同的问题,这似乎是一个干净的解决方案。

贷记到>> https://github.com/dotnet/efcore/issues/11586

我进行了一些更改,因此现在可以与.Net Core 3.1 +(在.Net 5中测试)一起使用,并且还添加了此方法 SaveChangesWithIdentityInsert

// Upload to GCS
return bucket.upload(thumbPath, {
  destination: join(bucketDir, thumbName),
    metadata: {
      cacheControl: "public, max-age=1209600",
      metadata: {
        firebaseStorageDownloadTokens: 'token'
      }
    }
});

用法

    public static class IdentityHelpers
{
    public static Task EnableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, enable: true);
    public static Task DisableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, enable: false);

    private static Task SetIdentityInsert<T>(DbContext context, bool enable)
    {
        var entityType = context.Model.FindEntityType(typeof(T));
        var value = enable ? "ON" : "OFF";
        return context.Database.ExecuteSqlRawAsync(
            $"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}");
    }

    public static void SaveChangesWithIdentityInsert<T>(this DbContext context)
    {
        using var transaction = context.Database.BeginTransaction();
        context.EnableIdentityInsert<T>();
        context.SaveChanges();
        context.DisableIdentityInsert<T>();
        transaction.Commit();
    }

}

答案 5 :(得分:1)

为了使用DbContext向对象图添加相关实体,我使用了DbCommandInterceptor,它会自动为相关表格设置INSERT_IDENTITY ON,然后在{插入。这适用于手动设置的ID和OFF。我在集成测试中使用了它,但是在进行性能优化之后,在某些情况下它可能适合于生产代码。 Here is my answer to a similar SO question which explains the details

答案 6 :(得分:0)

以下解决方案对我有用。(Link) 我在下面添加了注释。并删除了[Key]注释。

[KeyAttribute()]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }

可以根据实体框架版本更改名称空间。对于实体框架,核心名称空间为System.ComponentModel.DataAnnotations.Schema 由于我在一个新项目中进行过尝试,因此我没有面临数据迁移的麻烦。

答案 7 :(得分:0)

使用“ SET IDENTITY_INSERT [table] ON / OFF”进行交易

    public static void TranslateDatabase(ref BDVContext bdvContext) 
    {
        bdvContext.Foro.RemoveRange(bdvContext.Foro);
        bdvContext.SaveChanges();

        using (var transaction = bdvContext.Database.BeginTransaction()) 
        { 

            bdvContext.Database.ExecuteSqlRaw("SET IDENTITY_INSERT [dbo].[Foro] On");

            using (old_balsaContext db = new old_balsaContext()) 
            {
                long id = 0;
                foreach (ForoA77 post in db.ForoA77.Where(x => x.Fecha > new DateTime(2000,1,1) & x.IdPadre == 0 ) )
                {
                    bdvContext.Foro.Add(new Foro 
                    {
                          Id = ++id
                        , ParentId = 0
                        , EditId = 0
                        , IdDomains = 2
                        , UserNick = post.IdUsuario == 1 ? bdvContext.Users.Where(x => x.Id == 2).Single().User : post.Nick?? ""
                        , IdUsers = post.IdUsuario == 1 ? (int?)2 : null
                        , Title = post.Asunto?? ""
                        , Text = post.Texto?? ""
                        , Closed = post.Cerrado?? false
                        , Banned = post.Veto?? false
                        , Remarqued = post.Remarcado?? false
                        , Deleted = false
                        , Date = post.Fecha?? new DateTime(2001,1,1)
                    });
                }
            }

            bdvContext.SaveChanges();

            bdvContext.Database.ExecuteSqlRaw("SET IDENTITY_INSERT [dbo].[Foro] Off");

            transaction.Commit();
        }
    }

注意,我的实体框架是通过逆向工程生成的

答案 8 :(得分:0)

另一种方法是使用ExecuteSqlRaw。与ExecuteSqlInterpolated不同,您不必将传递的字符串转换为可格式化的字符串类型。

using (var db = new AppDbContext())
using (var transaction = db.Database.BeginTransaction())
{
    var user = new User {Id = 123, Name = "Joe"};
    db.Users.Add(user);
    db.Database.ExecuteSqlRaw("SET IDENTITY_INSERT MyDB.Users ON");
    db.SaveChanges();
    db.Database.ExecuteSqlRaw("SET IDENTITY_INSERT MyDB.Users OFF");
    transaction.Commit();

`}