2017-02-02 5 views
7

Это запрос:Почему эта вставка 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_INSERT установлен в положение ON или когда пользователь репликации , вставляя в колонку идентификации NOT FOR REPLICATION.

+2

На основании этого ответа [Entity Framework IDENTITY_INSERT ПО не работает] (http://stackoverflow.com/a/16215233/6741868), вы обратились Удостоверение Вставка на только вкратце, и он не включал вставку. Если это возможно, попробуйте создать большую строку запроса sql, начиная с '' SET IDENTITY_INSERT Test.Items ON; "', за которой следует 'INSERT INTO ...' и, наконец, '' SET IDENTITY_INSERT Test.Items OFF; "' (Все в один запрос). Или вы можете взглянуть на [TransactionScope Class] (https://msdn.microsoft.com/en-us/library/system.transactions.transactionscope (v = vs.110) .aspx). –

+0

@KeyurPATEL Использование прямого запроса 'INSERT' действительно поражает цель ORM. Я предпочитаю вариант транзакции, но также предпочитаю встроенную транзакцию, как в ответе Ананды. – ProfK

+0

@ProfK: Для исключения, можете ли вы обратиться к разработчику модели (edmx) и выбрать идентификатор свойства (это столбец Identity) и изменить StoreGenratedPattern на None вместо Identity? Вы должны изменить это, поскольку вы вставляете столбец идентификатора. Это должно исправить исключение. – Rangesh

ответ

1

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

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

Я думаю, что мы можем достичь этого путем перехвата команды SQL и изменив текст команды
(вы можете наследовать от DbCommandInterceptor ВОПОГ 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 являются хорошая абстракция, и я очень люблю их, но я не думаю, что это имеет смысл, чтобы попытаться «ч ack "для поддержки операций нижнего уровня (ближе к db).
Я стараюсь избегать хранимых процедур, но я думаю, что в этом (как вы сказали, исключительном) случае я думаю, вы должны использовать один

+0

. С другой стороны, я думаю, что я удалю это. Я не думаю, что вы можете получить здесь идентификатор ... –

+0

Ну, вы можете добавить свойство в свой контекст, а затем получить к нему доступ из interceptionContext.DbContexts.I не удалят ответ, но я надеюсь, что есть лучшее решение ... –

+0

Техник во мне любит идею перехвата, но хранит proc, вероятно, самый опрятный ответ. – ProfK

4

В соответствии с этим предыдущим Question вам необходимо начать транзакцию своего контекста. После сохранения изменения вы должны также скопировать столбец «Идентификация вставки» и, наконец, вам нужно будет совершить транзакцию.

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

Я не чтить теги вопроса, говоря это о EF6.
Этот ответ будет работать для EF Ядра

Реальный преступник здесь не хватает сделка, но небольшое неудобство, что 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 будет связан с подключением.

+0

Я получаю исключение, пытающееся это сделать, и с другим ответом. Подробности см. В моем редактировании в OP. – ProfK

+0

Упс. Я как-то принял EF Core .. Мой код работает в EF Core :) - Или работал до вашего редактирования – TGlatzer

+0

Ха-ха, рок и трудное место. Код, измененный мной, компилируется и запускается для меня на EF 6, но по-прежнему не выполняет то, что предназначено. – ProfK

0

Даже если вы отключите IDENTITY_INSERT, вы только что сказали SQL, что я пришлю вам Identity, вы не сказали сущностной структуре отправлять Identity на SQL-сервер.

Так в основном, вы должны создать 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(); 
    } 
} 
+0

Мне нравится ваше мышление здесь, но, похоже, вы не пробовали свой код. Повторная настройка 'IdentityItem' приводит к изменению внутренней модели EF, поэтому запрос выдает следующее исключение:' System.InvalidOperationException: модель, поддерживающая контекст «AppDbContext», изменилась с момента создания базы данных. И вы не можете связать 'HasDatabaseGeneratedOption' с' HasKey', я должен был сделать это, как мое редактирование в вашем коде. – ProfK

+1

@ProfK как насчет сейчас? Создавая два разных контекста, это приведет к тому, что модель будет создана один раз для каждого типа контекста. Извините, я забыл о кеше. Если это не работает, то альтернативой является создание двух отдельных контекстов. –

+1

Я думаю, что это рабочее решение. В любом случае, это неплохая идея иметь специализированные контексты для специальных задач или для нескольких разделов приложения. –

1

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

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

+0

Но даже тогда мне придется выполнять миграцию каждый раз, когда я использую другой один из двух контекстов. Если столбец является столбцом «Identity», и я использую контекст с «DatabaseGeneratedOption.None», я получу исключение из db, когда попытаюсь вставить значение в этот столбец. Я считаю, что лучше использовать хранимую процедуру для очень редкого события, в котором мне нужна вставка идентификатора. – ProfK

+1

После нескольких попыток я остановился, чтобы перенести данные с помощью EF. Я переношу данные с помощью ADO.Net. Хранимые процедуры - это самый быстрый (лучший) способ, если вам не нужно настраивать таргетинг на разные СУБД. – bubi

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 

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