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

9 .net asp.net-mvc entity-framework

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

当IDENTITY_INSERT设置为OFF时,无法在表'Persons'中为identity列插入显式值.

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

[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
Run Code Online (Sandbox Code Playgroud)

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

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

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

SAN*_*009 11

不得不处理同样的问题,这似乎是一个干净的解决方案。

归功于 >> https://github.com/dotnet/efcore/issues/11586

我进行了一些更改,因此它现在适用于 .Net Core 3.1 +(在 .Net 5 中测试)并添加了此方法SaveChangesWithIdentityInsert

    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();
    }

}
Run Code Online (Sandbox Code Playgroud)

用法

        var data = new MyType{SomeProp= DateTime.Now, Id = 1};
            context.MyType.Add(data);
        context.SaveChangesWithIdentityInsert<MyType>();
Run Code Online (Sandbox Code Playgroud)

  • 这个解决方案包含一些不错的想法。然而,该实现存在一些与 Task/async/await 的误用相关的缺陷。SaveChangesWithIdentityInsert 方法不返回 Task,也不等待对 EnableIdentityInsert 和 DisableIdentityInsert 的调用。这可能会导致不良的副作用。以下实现支持异步/等待和非等待范例。/sf/answers/4601298201/ (2认同)

sny*_*m42 10

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

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();
}
Run Code Online (Sandbox Code Playgroud)

  • @Sras SQL Server 一次只允许为一张表启用 IDENTITY_INSERT。如果您尝试在为前一个表关闭它之前为另一个表设置它,SQL Server 将抛出错误:https://learn.microsoft.com/en-us/sql/t-sql/statements/set-identity- insert-transact-sql?view=sql-server-ver15#remarks (2认同)

Ale*_*xei 8

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

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

数据库上下文将BEGIN TRANSET IDENTITY_INSERT发布后生成.这将使事务的插入失败,因为IDENTITY_INSERT似乎影响会话/事务级别的表.

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

以下是一些在密钥级别(与表级别相对)进行种子处理的有用代码:

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);
}
Run Code Online (Sandbox Code Playgroud)

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);
    });
}
Run Code Online (Sandbox Code Playgroud)

  • 此代码使用`context.Model.FindEntityType(typeof(T)).Relational().TableName` 绕过模型类名与表名相同的假设:https://github.com/aspnet/EntityFrameworkCore /问题/11586 (2认同)

Moh*_*ari 6

@Steve Nyholm 的回答是可以的,但在 .Net core 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();
}
Run Code Online (Sandbox Code Playgroud)


Ric*_*ahl 5

另一种方法是显式打开连接 then 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();
Run Code Online (Sandbox Code Playgroud)

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

这与 Steve 对事务的响应在事务保持连接活动时起作用的原因相同。

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


Qud*_*dus 5

另一种方法是使用 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();
}
Run Code Online (Sandbox Code Playgroud)


Nin*_*oss 5

@sanm2009 提出的解决方案包含一些不错的想法。

然而,该实现存在一些与 Task/async/await 误用相关的缺陷。

SaveChangesWithIdentityInsert 方法不返回 Task,也不等待对 EnableIdentityInsert 和 DisableIdentityInsert 的调用。

这可能会导致不希望的副作用。

以下实现同时支持 async/await 和 non-awaitable 范式。

#region IDENTITY_INSERT

        public static void EnableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, true);
        public static void DisableIdentityInsert<T>(this DbContext context) => SetIdentityInsert<T>(context, false);

        private static void SetIdentityInsert<T>([NotNull] DbContext context, bool enable)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));
            var entityType = context.Model.FindEntityType(typeof(T));
            var value = enable ? "ON" : "OFF";
            context.Database.ExecuteSqlRaw($"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}");
        }

        public static void SaveChangesWithIdentityInsert<T>([NotNull] this DbContext context)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));
            using var transaction = context.Database.BeginTransaction();
            context.EnableIdentityInsert<T>();
            context.SaveChanges();
            context.DisableIdentityInsert<T>();
            transaction.Commit();
        }

        #endregion 

        #region IDENTITY_INSERT ASYNC

        public static async Task EnableIdentityInsertAsync<T>(this DbContext context) => await SetIdentityInsertAsync<T>(context, true);
        public static async Task DisableIdentityInsertAsync<T>(this DbContext context) => await SetIdentityInsertAsync<T>(context, false);

        private static async Task SetIdentityInsertAsync<T>([NotNull] DbContext context, bool enable)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));
            var entityType = context.Model.FindEntityType(typeof(T));
            var value = enable ? "ON" : "OFF";
            await context.Database.ExecuteSqlRawAsync($"SET IDENTITY_INSERT {entityType.GetSchema()}.{entityType.GetTableName()} {value}");
        }

        public static async Task SaveChangesWithIdentityInsertAsync<T>([NotNull] this DbContext context)
        {
            if (context == null) throw new ArgumentNullException(nameof(context));
            await using var transaction = await context.Database.BeginTransactionAsync();
            await context.EnableIdentityInsertAsync<T>();
            await context.SaveChangesAsync();
            await context.DisableIdentityInsertAsync<T>();
            await transaction.CommitAsync();
        }


        #endregion 
Run Code Online (Sandbox Code Playgroud)

  • 您应该将第二个“await context.EnableIdentityInsertAsync&lt;T&gt;();”替换为“DisableIdentityInsertAsync”。:) (2认同)