2014/01/03

Entity Framework で楽観的同時実行制御を行う【コードファースト編】


Entity Framework を使用してデータの更新を行う際、他のユーザーが既にデータを更新していた場合、「楽観的同時実行制御」という制御を行います。

「楽観的同時実行制御」とは、データ参照時に排他ロックをかけず、データの更新時に他のユーザーによってデータが更新されていた場合にエラーする制御方法です。

このエントリーでは、Entity Framework の「コードファースト」で楽観的同時実行制御を行う方法を紹介します。

前提条件

前提条件は以下のようになります。

  • Visual Studio Express 2013 for Windows Desktop
  • Entity Framework 6.0.2
  • SQL Server  2012 Express
  • コンソールアプリケーション
  • データベースはコードから作成する

前準備

Visual Studio Express 2013 for Windows Desktop を起動して、コンソールアプリケーションプロジェクトを作成します。

プロジェクトを右クリックして、[NuGet パッケージの管理] をクリックして、NuGet パッケージ管理画面を表示します。

そして、Entity Framework をインストールします。

pic01 

コードファーストからデータベースを作成

Item クラスを作成します。TimeStamp アノテーションが付いているフィールドの値で楽観的同時実行制御を行います。TimeStamp フィールドの型が byte[] なのは、SQL Server の rowversion に合わせています。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;

namespace EFCodeFirst
{
  public class Item
  {
    public int Id { get; set; }

    public string Name { get; set; }

    public int Price { get; set;}

    [Timestamp]
    public byte[] TimeStamp { get; set; }
  }
}

ItemContext クラスを作成します。データベースとクラスの関連づけを行います。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;

namespace EFCodeFirst
{
  public class ItemContext:DbContext
  {
    public DbSet<Item> Items { get; set; }
  }
}

App.config にデータベースへの接続文字列を追加します。名前が、ItemContext クラスと同じになっている必要があります。

<connectionStrings>
  <add name="ItemContext"
       connectionString="Data Source=(local)\SQLEXPRESS;Initial Catalog=Test;Integrated Security=True;"
       providerName="System.Data.SqlClient" />
</connectionStrings>

データベース・テーブルの作成、初期データの投入を行います。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;

namespace EFCodeFirst
{
  class Program
  {
    static void Main(string[] args)
    {
      //データベース初期化
      Database.SetInitializer(new DropCreateDatabaseIfModelChanges<ItemContext>());

      using (var context = new ItemContext())
      {
        var item1 = new Item { Name = "はさみ", Price = 100 };
        context.Items.Add(item1);

        var item2 = new Item { Name = "けしごむ", Price = 100 };
        context.Items.Add(item2);

        var item3 = new Item { Name = "えんぴつ", Price = 100 };
        context.Items.Add(item3);

        context.SaveChanges();
      }
    }
  }
}

SQL Server オブジェクトエクスプローラーで確認すると、Test データベースと Items テーブルが正しく作成されていますね。TimeStamp フィールドも rowversioin 型になっています。

pic02

データも正しく作成されています。

pic03

データ更新プログラム

データ更新プログラムは以下のようになります。競合発生時は、System.Data.Entity.Infrastructure.DbUpdateConcurrencyException が発生します。

 

(追記)

ObjectContext のメソッドがほとんど表示できない問題の対処方法が分かりました。これで競合発生時に、Refresh を行うことで対応ができるようになります。

 

 

・Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;

namespace EFCodeFirst
{
  class Program
  {
    static void Main(string[] args)
    {
      //データベース初期化
      Database.SetInitializer(new DropCreateDatabaseIfModelChanges<ItemContext>());

      //データベース・テーブル作成、初期データ投入
      using (var context = new ItemContext())
      {
        var items = from t in context.Items
              select t;

        foreach (var item in items)
        {
          item.Price = 200;
        }

        //処理を止めている間にデータを外部から更新する
        Console.ReadKey();

        try
        {
          context.SaveChanges();

          Console.WriteLine("正常終了");

        }
        catch (DbUpdateConcurrencyException ex)
        {
          Console.WriteLine("競合発生");

          Console.WriteLine(ex.Message);
        }

        Console.ReadKey();
      }
    }
  }
}

正常時動作確認

正常終了時は、コンソールに以下のように出力されたので問題ありませんね。

正常終了

データも正しく更新されています。

pic04

競合発生時動作確認

コンソールでキー入力待ちの時に、外部からデータを更新した場合は以下のようにコンソールにエラーメッセージが出力されましたので正しく処理されています。

競合発生
Store update, insert, or delete statement affected an unexpected number of rows(0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.

おわりに

Entity Framework のデータファーストでの楽観的同時実行制御を見てきましたがいかがでしたでしょうか。モデルファーストよりも簡単に実現できますね。

ただ、競合ができるだけ発生しないような設計にする必要はあるかと思います。

関連エントリー

参考サイト


スポンサーリンク


このエントリーをはてなブックマークに追加




Twitter ではブログにはない、いろんな情報を発信しています。


コメント

コメントを書く



プロフィール

  • 名前:fnya
    経歴:
    SE としての経験は15年以上。様々な言語と環境で業務系システム開発を行い、セキュリティ対策などもしていました。現在は趣味SE。

    Twitter では、ブログでは取り上げない情報も公開しています。


    ブログについて

    このブログは、IT、スマートフォン、タブレット、システム開発などに関するさまざまな話題を取り上げたり、雑感などをつづっています。

    >>ブログ詳細
    >>自作ツール
    >>運営サイト
    >>Windows 10 まとめ

    Twitter のフォローはこちらから Facebook ページはこちら Google+ページはこちら RSSフィードのご登録はこちらから