2016-04-20 5 views
2

エンティティフレームワークv6を使用しています。アトミックInsertまたはSelectを実行してレコードが存在しない場合は、サーバーのファームまたは複数のスレッド)私は一意のキー制約違反がないことを保証することができます。エンティティフレームワークでInsertOrSelectを挿入する方法

私はこのような表と2つのプロパティを持つ対応する単純なモデルを持つ簡単な例を持っています。私はEntity Frameworkのコードを記述する場合

CREATE TABLE [dbo].[NewItem] 
(
    [ID] [int] IDENTITY(1,1) NOT NULL, 
    [Name] [varchar](40) NOT NULL, 

    CONSTRAINT [PK_NewItem] PRIMARY KEY CLUSTERED ([ID] ASC), 
    CONSTRAINT [IX_NewItem] UNIQUE NONCLUSTERED ([Name] ASC) 
) 

することは、私は、オブジェクトが偽.Any戻った後に挿入されないことを保証することはできません。

using (var myContext = new MyContext()) 
{ 
    using (var transaction = myContext.Database.BeginTransaction()) 
    { 
     if (!myContext.NewItems.Any(item => item.Name == identifier)) 
     { 
      var newItem = new NewItem { Name = identifier }; 
      myContext.NewItems.Add(newItem); 

      try 
      { 
       var result = myContext.SaveChanges(); 
      } 
      catch (Exception ex) 
      { 
       Debug.Print(ex.ToString()); 
       throw; 
      } 
     } 

     transaction.Commit(); 
    } 
} 

私はSqlCommandオブジェクトを介して直接SQL文を使用して、この同じコードを実行するためだった場合、これは私が私の知る限り、それは常に動作します言うことができるように使用してしまうものです。

using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString)) 
{ 
    using (var cmd = connection.CreateCommand()) 
    { 
     cmd.CommandText = "IF (NOT EXISTS(SELECT ID FROM NewItem WHERE Name = @name)) " + 
      " BEGIN " + 
      " INSERT INTO NewItem VALUES (@name) " + 
      " SELECT SCOPE_IDENTITY() AS ID " + 
      " END " + 
      " ELSE " + 
      " BEGIN " + 
      " SELECT ID FROM NewItem WHERE Name = @name " + 
      " END"; 
     var nameParam = new SqlParameter("@name", System.Data.SqlDbType.VarChar, 40); 
     nameParam.Value = Name; 
     cmd.Parameters.Add(nameParam); 

     connection.Open(); 
     var result = cmd.ExecuteScalar(); 
     connection.Close(); 

     Id = Convert.ToInt32(result); 
    } 
} 

が既に存在する場合、レコードは、私はちょうどあるものを得る、それが挿入されます存在していない場合、私は私の生SqlCommandように同じ動作を実行できることをEntity Frameworkを持ついくつかの方法があります原子的な操作の中にありますか?

これを実証するには、複数のサーバーをシミュレートするために使用したスレッドコードがあります。

private static ExecutionMode mode; 
private static ManualResetEventSlim wait; 
private static string identifier; 

private enum ExecutionMode 
{ 
    None = 0, 
    Entityframework, 
    RawSql 
} 

static void Main(string[] args) 
{ 
    //Comment out the relevant item to test 
    //mode = ExecutionMode.RawSql; 
    mode = ExecutionMode.Entityframework; 

    wait = new ManualResetEventSlim(false); 
    identifier = Guid.NewGuid().ToString(); 

    var threads = new List<Thread>(); 
    for (var i = 0; i < 20; i++) 
    { 
     var t = new Thread(RunCreate); 
     threads.Add(t); 
     t.Start(); 
    } 

    wait.Set(); 

    for (var i = 0; i < 20; i++) 
    { 
     var t = threads[i]; 
     t.Join(); 
    } 
} 

private static void RunCreate() 
{ 
    wait.Wait(); 
    switch (mode) 
    { 
     case ExecutionMode.Entityframework: 
      { 
       //perform the Entityframework code above 
      } 
      break; 
     case ExecutionMode.RawSql: 
      { 
       var i = new NewItem { Name = identifier }; 
       i.InsertOrSelect(); //This is just using the code for the raw SQL Statements 
      } 
      break; 
    } 
} 

例外をループex.Messageを表示し、Entityframeworkの経験exception->のInnerExceptionのInnerExceptionはSerializable tranaction分離してデフォルトのトランザクション分離レベル

BeginTransaction() 
A first chance exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' 
    occurred in EntityFramework.dll 
An error occurred while updating the entries. 
    See the inner exception for details. 
An error occurred while updating the entries. 
    See the inner exception for details. 
Violation of UNIQUE KEY constraint 'IX_NewItem'. Cannot insert duplicate key in object 'dbo.NewItem'. 
    The duplicate key value is (f32c6a59-1462-49c3-85e2-5126c96ad484). 

ヌルになるまで

BeginTransaction(System.Data.IsolationLevel.Serializable) 
A first chance exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' 
    occurred in EntityFramework.dll 
Transaction (Process ID 59) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction. 

答えて

1

いずれもこれらは動作することが保証されています。あなたが欠けている部分はTransaction Isolation Levelです。

SQL Serverの既定の分離レベルはREAD COMMITTEDです。これには最小限のロック保護しかありません。これは、条件が評価された後、新しい行が挿入される前に、別のコマンドが新しい項目を挿入する可能性があることを意味します。生のSQLサーバーコマンドはです。は、このシナリオに遭遇する可能性が低く、コマンド間で往復することなく処理されるため、シナリオに遭遇する可能性は低くなります。

アイソレーションレベルをシリアライズ可能に設定すると、アイテムを挿入するか、既存のアイテムを選択することが保証されます。 Entity Frameworkのでは、あなたのコードは次のようになります。

using (var myContext = new MyContext()) 
{ 
    using (var transaction = myContext.Database.BeginTransaction(System.Data.IsolationLevel.Serializable)) 
    { 
     if (!myContext.NewItems.Any(item => item.Name == identifier)) 
     { 
      var newItem = new NewItem { Name = identifier }; 
      myContext.NewItems.Add(newItem); 
      try 
      { 
       var result = myContext.SaveChanges(); 
      } 

      catch (Exception ex) 
      { 
       Debug.Print(ex.ToString()); 
       throw; 
      } 
     } 
     transaction.Commit(); 
    } 
} 
+0

私だけでBeginTransactionに 'System.Data.IsolationLevel.Serializable'を追加しようとしたと私は違反と同じ結果を得ます。 –

+0

この違反は、コードが1つのサーバー上で1つのスレッドでのみ実行されている場合に発生しますか?あなたが使用している正確なコードを投稿したコードはありますか?私はそれが必要な詳細だけを表示するように変更されていると仮定します。 –

+0

違反は単一のスレッド/サーバーシナリオで発生しません* –

関連する問題