13

这是查询:

using (var db = new AppDbContext())
{
    var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
    db.IdentityItems.Add(item);
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
    db.SaveChanges();
}

执行时,Id新表上插入的记录的 仍然为 1。

新:当我使用交易或 TGlatzer 的答案时,我得到了异常:

当 IDENTITY_INSERT 设置为 ON 或复制用户插入 NOT FOR REPLICATION 标识列时,必须为表“项目”中的标识列指定显式值。

4

8 回答 8

12

根据这个先前的问题,您需要开始您的上下文事务。保存更改后,您还必须重新声明 Identity Insert 列,最后您必须提交事务。

using (var db = new AppDbContext())
using (var transaction = db .Database.BeginTransaction())
{
    var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
    db.IdentityItems.Add(item);
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
    db.SaveChanges();
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items OFF");
    transaction.Commit();
}
于 2017-02-02T07:59:23.563 回答
8

我不尊重关于 EF6 的问题的标签。
此答案适用于 EF Core

真正的罪魁祸首不是缺少事务,而是小不便,即Database.ExectueSqlCommand()在之前未显式打开时不会保持连接打开。

using (var db = new AppDbContext())
{
    var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
    db.IdentityItems.Add(item);
    db.Database.OpenConnection();
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
    db.SaveChanges();
}

也可以,因为SET IDENTITY_INSERT [...] ON/OFF将绑定到您的连接。

于 2017-02-02T15:52:33.483 回答
4

要强制 EF 写入实体的 ID,您必须将 ID 配置为不存储生成,否则 EF 将永远不会在插入语句中包含 ID。

因此,您需要动态更改模型并根据需要配置实体 ID。
问题是模型被缓存并且在运行中更改它非常棘手(我很确定我已经做到了,但实际上我找不到代码,可能我把它扔掉了)。最短的方法是创建两个不同的上下文,您可以在其中以两种不同的方式配置实体,如DatabaseGeneratedOption.None(当您需要编写 ID 时)和DatabaseGeneratedOption.Identity(当您需要自动编号 ID 时)。

于 2017-02-04T12:01:20.640 回答
3

答案适用于实体框架 6 只需在事务外使用 IDENTITY_INSERT

using (var db = new AppDbContext())
{
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
    using (var transaction = db .Database.BeginTransaction())
    {
       var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
       db.IdentityItems.Add(item);
       db.SaveChanges();
       transaction.Commit();
    }
    db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items OFF");
}
于 2019-12-12T21:46:09.393 回答
3

我有一个类似的问题。在我的生产代码中,实体依赖于身份生成。但是对于集成测试,我需要手动设置一些 ID。在我不需要明确设置它们的地方,我在我的测试数据构建器中生成了它们。为了实现这一点DbContext,我在生产代码中创建了一个继承,并为每个实体配置了身份生成,如下所示:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity<Entity1>().Property(e => e.Id).ValueGeneratedNever();
    modelBuilder.Entity<Entity2>().Property(e => e.Id).ValueGeneratedNever();
    ...
}

但这还不够,我不得不禁用 SQL Server IDENTITY_INSERT。这在单个表中插入数据时有效。但是,当您有相互关联的实体并且想要插入对象图时,这将失败DbContext.SaveChanges()。原因是根据SQL Server 文档IDENTITY_INSERT ON,您在会话期间一次只能拥有一个表。我的同事建议使用与此问题的其他答案DbCommandInterceptor类似的a 。我让它只工作,但这个概念可以进一步扩展。目前它拦截并修改单个. 可以优化代码以使用Span.SliceINSERT INTOINSERT INTODbCommand.CommandText为了避免由于字符串操作而导致过多的内存,但是由于我找不到Split方法,所以我没有为此投入时间。无论如何,我正在使用它DbCommandInterceptor进行集成测试。如果您觉得有帮助,请随意使用它。

/// <summary>
/// When enabled intercepts each INSERT INTO statement and detects which table is being inserted into, if any.
/// Then adds the "SET IDENTITY_INSERT table ON;" (and same for OFF) statement before (and after) the actual insertion.
/// </summary>
public class IdentityInsertInterceptor : DbCommandInterceptor
{
    public bool IsEnabled { get; set; }

    public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
    {
        if (IsEnabled)
        {
            ModifyAllStatements(command);
        }

        return base.ReaderExecuting(command, eventData, result);
    }

    private static void ModifyAllStatements(DbCommand command)
    {
        string[] statements = command.CommandText.Split(';', StringSplitOptions.RemoveEmptyEntries);
        var commandTextBuilder = new StringBuilder(capacity: command.CommandText.Length * 2);

        foreach (string statement in statements)
        {
            string modified = ModifyStatement(statement);
            commandTextBuilder.Append(modified);
        }

        command.CommandText = commandTextBuilder.ToString();
    }

    private static string ModifyStatement(string statement)
    {
        const string insertIntoText = "INSERT INTO [";
        int insertIntoIndex = statement.IndexOf(insertIntoText, StringComparison.InvariantCultureIgnoreCase);
        if (insertIntoIndex < 0)
            return $"{statement};";

        int closingBracketIndex = statement.IndexOf("]", startIndex: insertIntoIndex, StringComparison.InvariantCultureIgnoreCase);
        string tableName = statement.Substring(
            startIndex: insertIntoIndex + insertIntoText.Length,
            length: closingBracketIndex - insertIntoIndex - insertIntoText.Length);

        // we should probably check whether the table is expected - list with allowed/disallowed tables
        string modified = $"SET IDENTITY_INSERT [{tableName}] ON; {statement}; SET IDENTITY_INSERT [{tableName}] OFF;";
        return modified;
    }
}

于 2020-02-28T10:49:22.770 回答
2

这绝不能在生产代码中使用,这只是为了好玩
我看到我的仍然是公认的答案,再次,不要使用它(解决这个问题),检查下面的其他答案

我不建议这样做,因为它是一个疯狂的黑客,但无论如何。

我想我们可以通过拦截SQL命令并更改命令文本来实现
(可以继承DbCommandInterceptor并覆盖ReaderExecuting)

我目前没有一个可行的例子,我必须去,但我认为这是可行的

示例代码

    public class MyDbInterceptor : DbCommandInterceptor
    {
        public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {

            if (is your table)
            {
                command.CommandText = "Set Identity off ,update insert into ,Set Identity off"
                return;
            }
            base.ReaderExecuting(command, interceptionContext);

        }

    }

ORM 是一个很好的抽象,我真的很喜欢它们,但我认为尝试“破解”它们以支持较低(更接近 db)级别的​​操作是没有意义的。
我尽量避免存储过程,但我认为在这种情况下(正如你所说的例外)我认为你应该使用一个

于 2017-02-04T12:34:51.630 回答
1

即使你关闭了IDENTITY_INSERT,你也只是告诉 SQL 我会给你发送 Identity,你并没有告诉实体框架发送 Identity 给 SQL server。

所以基本上,你必须创建 DbContext 如下所示..

// your existing context
public abstract class BaseAppDbContext : DbContext { 


    private readonly bool turnOfIdentity = false;
    protected AppDbContext(bool turnOfIdentity = false){
        this.turnOfIdentity = turnOfIdentity;
    }


    public DbSet<IdentityItem> IdentityItems {get;set;}

    protected override void OnModelCreating(DbModelBuilder modelBuilder){
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<IdentityItem>()
           .HasKey( i=> i.Id )

           // BK added the "Property" line.
           .Property(e => e.Id)
           .HasDatabaseGeneratedOption(
               turnOfIdentity ?
                   DatabaseGeneratedOption.None,
                   DatabaseGeneratedOption.Identity
           );

    }
}

public class IdentityItem{

}


public class AppDbContext: BaseAppDbContext{
    public AppDbContext(): base(false){}
}

public class AppDbContextWithIdentity : BaseAppDbContext{
    public AppDbContext(): base(true){}
}

现在这样用...

using (var db = new AppDbContextWithIdentity())
{
    using(var tx = db.Database.BeginTransaction()){
       var item = new IdentityItem {Id = 418, Name = "Abrahadabra" };
       db.IdentityItems.Add(item);
       db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items ON;");
       db.SaveChanges();
       db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT Test.Items OFF");
       tx.Commit();
    }
}
于 2017-02-04T09:24:58.750 回答
0

我有一个非常相似的问题。

解决方案类似于:

db.Database.ExecuteSqlCommand("disable trigger all on  myTable ;") 
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT myTable  ON;");
db.SaveChanges();
db.Database.ExecuteSqlCommand("SET IDENTITY_INSERT myTable  OFF");
db.Database.ExecuteSqlCommand("enable trigger all on  myTable ;") 

就我而言,该消息Explicit value must be specified for identity...是因为在插入时调用了一个触发器并将插入其他内容。

ALTER TABLE myTable NOCHECK CONSTRAINT all

也可以有用

于 2017-11-15T12:57:41.870 回答