Entity Framework で楽観的同時実行制御を行う【モデルファースト編】
Entity Framework を使用してデータの更新を行う際、他のユーザーが既にデータを更新していた場合、「楽観的同時実行制御」という制御を行います。
「楽観的同時実行制御」とは、データ参照時に排他ロックをかけず、データの更新時に他のユーザーによってデータが更新されていた場合にエラーする制御方法です。
このエントリーでは、Entity Framework の「モデルファースト」で楽観的同時実行制御を行う方法を紹介します。
前提条件
前提条件は以下のようになります。
- Visual Studio Express 2013 for Windows Desktop
- Entity Framework 6.0
- SQL Server 2012 Express
- コンソールアプリケーション
- データベースはモデルから作成する
前準備
Visual Studio Express 2013 for Windows Desktop を起動して、コンソールアプリケーションプロジェクトを作成します。
モデルファーストからデータベースを作成
プロジェクト名を右クリックして、[追加] – [新しい項目] から、[データ] – [ADO.NET Entity Data Model] を選択し、空のデータモデルを作成します(Model1.edmx)。
そうすると空のデータモデル図が表示されるので、以下のような Item エンティティを作成します。エンティティセット名は「Items」とします(テーブル名になります)。
プロパティ | 型 |
Id | Int32 |
Name | String |
Price | Int32 |
TimeStamp | Binary |
すると以下のようなダイアログが作成されます。
ここで「TimeStamp」を選択して、プロパティウィンドウで「同時実行モード」を「fixed」にします。この「TimeStamp」にデータのバージョンが記録され、楽観的同時実行制御を実現します。
次に、ダイアログ図のなにもないところをクリックして、Model1 のプロパティをプロパティウィンドウに表示します。そして、「エンティティコンテナー名」を「ItemEntites」に変更します。
ダイアログ図のなにもないところを右クリックして、[モデルからデータベースを作成] をクリックして、データベース作成ウィザードを起動します。
[新しい接続] をクリックします。
サーバー名に「(local)\SQLEXPRESS」、データベース名に「Test」と入力して、[OK] をクリックします。
[次へ] をクリックします。
Entity Framework 6.0 が選択されていることを確認して、[次へ] をクリックします。
テーブル作成の DDL が表示されます。左上の実行ボタンをクリックします。SQL Server の認証画面が表示されるのでログインしてください。
実行後になってしまうのですが、Model1.edmx.sql を開いて、以下のように変更し、再度 DDL を実行してください。
[TimeStamp] varbinary(max) NOT NULL
を
[TimeStamp] rowversion
に変更。
SQL Server の rowversion 型は、データが更新されたら自動的に内容が書き換わる型なのですが、Entity Framework には対応する型がないので、とりあえず Binary で作成しました。
データの型としては、Binary と rowversion は同じなので、自動生成されたクラスは問題なく使用できます。
SQL Server オブジェクトエクスプローラーで表示すると、問題なくデータベースとテーブルが作成されていることが確認できます。
無事にテーブルまで作成できたので、サンプルデータを作成します。
Items テーブルを右クリックして [データの表示] をクリックします。そして、以下のようにデータを入力します(Id と TimeStamp は自動入力)。
データ更新プログラム
データ更新プログラムは以下のようになります。競合発生時は、System.Data.Entity.Infrastructure.DbUpdateConcurrencyException が発生します。MSDN ライブラリでは、System.Data.OptimisticConcurrencyException が発生するとあるのですが、この違いは何でしょう。.NET 4.0 と 4.5 で仕様が変わったのでしょうか?
まあ、それはともかく、以下のプログラムで楽観的同時実行制御を行うことができます。
(追記)
ObjectContext のメソッドがほとんど表示できない問題の対処方法が分かりました。これで競合発生時に、Refresh を行うことで対応ができるようになります。
・Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Data.Objects;
using System.Data.Entity.Infrastructure;
namespace EFModelFirst
{
class Program
{
static void Main(string[] args)
{
using (var entities = new ItemEntities())
{
var items = from t in entities.Items
select t;
foreach (var item in items)
{
item.Price = 200;
}
//処理を止めている間にデータを外部から更新する
Console.ReadKey();
try
{
entities.SaveChanges();
Console.WriteLine("正常終了");
}
catch (DbUpdateConcurrencyException ex)
{
Console.WriteLine("データ更新で競合発生");
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
}
}
}
正常時動作確認
正常終了時は、コンソールに以下の文字が出力されましたので問題ありません。
正常終了
また、データも以下のように正しく更新されています。
異常時動作確認
コンソールでキー入力待ちの時に、外部からデータを更新した場合は以下のようにコンソールにエラーメッセージが出力されましたので正しく処理されています。
データ更新で競合発生
ストアの更新、挿入、または削除ステートメントが予期しない数 (0) の行に影響しました。エンティティが読み込まれた以降に、エンティティが変更されたか、削除されています。ObjectStateManager のエントリを最新の情報に更新してください。
おわりに
モデルファーストで、Entity Framework で楽観的同時実行制御を行う方法を見てきましたがいかがでしたでしょうか。
個人的には、競合ができるだけ発生しないような設計を行うことが大事だと思います。
関連エントリー
参考サイト
スポンサーリンク
Twitter ではブログにはない、いろんな情報を発信しています。
@fnyaさんをフォロー
コメント