web-dev-qa-db-ja.com

IDENTITY_INSERTを使用したこのEF挿入が機能しないのはなぜですか?

これはクエリです:

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 ID列に挿入されている場合は、テーブル 'Items'のID列に明示的な値を指定する必要があります。

10
ProfK

これは製品コードでは決して使用しないでください、それは楽しみのためだけです
私がまだ受け入れられている回答であることがわかります。これを使用しないでください(この問題を解決するため)、以下の他の回答を確認してください

クレイジーなハックなので、これはお勧めしませんが、とにかく。

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に近い)レベルの操作をサポートするためにORMを「ハッキング」しようとするのは理に適っていないと思います。
私はストアドプロシージャを避けようとしますが、(例外的に言ったように)このケースでは、1つを使用する必要があると思います

2
George Vovos

この以前の Question によると、コンテキストのトランザクションを開始する必要があります。変更を保存した後、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();
}
11
gdmanandamohon

私はこれが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は接続にバインドされるため、これも行います。

5
TGlatzer

エンティティのEF書き込みIDを強制するには、ストアが生成されないようにIDを構成する必要があります。そうしないと、EFがinsertステートメントにIDを含めません。

したがって、モデルをその場で変更し、必要に応じてエンティティIDを構成する必要があります。
問題は、モデルがキャッシュされており、その場で変更するのが非常に難しいことです(私はそれを実行したと確信していますが、実際にはコードを見つけることができません。おそらく捨ててしまいました)。 。最短の方法は、DatabaseGeneratedOption.None(IDを書き込む必要がある場合)とDatabaseGeneratedOption.Identity(自動採番IDが必要な場合)の2つの異なる方法でエンティティを構成する2つの異なるコンテキストを作成することです。

3
bubi

私は非常に似た問題を抱えていました。

解決策は次のようなものでした:

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

役に立つこともできます

0
Madlozoz

答えはEntity Framework 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");
}
0
pauluss

同様の問題がありました。私のプロダクションコードでは、エンティティはID生成に依存しています。しかし、統合テストでは、いくつかのIDを手動で設定する必要があります。明示的に設定する必要がない場合は、 テストデータビルダー で生成しました。これを実現するために、本番コードの1つを継承するDbContextを作成し、各エンティティのID生成を次のように構成しました。

_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のドキュメント のとおり、セッション中に一度に1つのテーブルに対してのみ_IDENTITY_INSERT ON_を使用できるためです。私の同僚は、 この質問に対する他の回答 に類似したDbCommandInterceptorの使用を提案しました。私はそれを_INSERT INTO_でのみ機能させるようにしましたが、コンセプトはさらに拡張できます。現在、単一の_INSERT INTO_内の複数の_DbCommand.CommandText_ステートメントをインターセプトして変更します。文字列操作によるメモリの過剰を避けるために、コードを最適化して Span.Slice を使用できますが、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;
    }
}

_
0
ceco

IDENTITY_INSERTをオフにしても、Identityを送信することをSQLに伝えただけで、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
Akash Kava