Почему эта вставка EF с IDENTITY INSERT не работает?

Это запрос:

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.

NEW: когда я использую либо транзакцию, либо ответ TGlatzer, я получаю исключение:

явное значение должно быть указано для столбца identity в таблице 'Items' когда IDENTITY_INSERT имеет значение ON или когда пользователь репликации вставка в удостоверение не для репликации колонна.

6 ответов


это никогда не должно использоваться в производственном коде, это просто для удовольствия

Я не предлагаю это, потому что это сумасшедший хак, но в любом случае.

Я думаю, что мы можем достичь этого, перехватив команду sql и изменив текст команды
(вы можете наследовать от DbCommandInterceptor adn ovveride ReaderExecuting)

У меня нет рабочего примера, в данный момент, и я должен идти, но я думаю, что это выполнимо

образец код

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

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

        }

    }

ORMs-хорошая абстракция, и мне они очень нравятся, но я не думаю, что имеет смысл пытаться "взломать" их, чтобы поддерживать операции более низкого(ближе к БД) уровня.
Я стараюсь избегать хранимых процессов, но я думаю, что в этом (как вы сказали, исключительном) случае я думаю, что вы должны использовать один


по этому вопрос вам нужно начать транзакцию вашего контекста. После сохранения изменения вы также должны повторно указать столбец 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();
}

чтобы заставить EF писать ID вашей сущности, вы должны настроить ID как не хранилище, иначе EF никогда не будет включать ID в инструкцию insert.

Итак, вам нужно изменить модель на лету и настроить идентификатор сущности по мере необходимости.
Проблема в том, что модель кэшируется и довольно сложно изменить ее на лету (я уверен, что сделал это, но на самом деле я не могу найти код, вероятно, я выбросил его). Самый короткий путь-создать два разных контексты, в которых вы настраиваете свою сущность двумя разными способами, как DatabaseGeneratedOption.None (когда вам нужно написать ID) и как DatabaseGeneratedOption.Identity (когда требуется код автонумерации).


Я не соблюдал теги вопроса, говорящего, что это касается 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 будет привязан к вашей связи.


даже если вы выключите IDENTITY_INSERT, вы только что сказали SQL, что я отправлю вам удостоверение, вы не сказали entity framework отправить удостоверение на 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();
    }
}

у меня была очень похожая проблема.

решение было что-то вроде:

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 ;") 

В моем случае было потому, что при вставке вызывается триггер и вставляет что-то еще.

ALTER TABLE myTable NOCHECK CONSTRAINT all

также может быть полезным