SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

実例で学ぶASP.NET Webフォーム業務アプリケーション開発のポイント

データの同時更新を防ぐための排他制御

実例で学ぶASP.NET Webフォーム業務アプリケーション開発のポイント 第6回

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

どちらの排他制御を選ぶか

 ASP.NET Webフォームアプリケーションは連載初回にも述べたように「ステートレス」であり、悲観的排他制御をしようにもロック状態をメモリ上に取っておくことができません。従って、単純な方法では悲観的排他制御を行うことができません。

 そのため、ASP.NET Webフォームアプリケーションでは、基本的には楽観的排他制御を選ぶことになります。

 もちろん、悲観的排他制御を行う方法がないわけではありません。例えば、データベースに処理中のデータの情報を登録することで、疑似的に悲観的排他制御を行うといった方法があります。

 ただし、処理が煩雑になってしまいますし、あまり一般的ではないため、今回は説明を割愛します。詳しく知りたい場合は以下の書籍の12章「業務排他制御による対話型トランザクション処理の開発」を参考にしてください。

  1. Microsoft Visual Studio 2005によるWebアプリケーション構築技法

 今回は楽観的排他制御に絞って、その実装方法を説明します。

楽観的排他制御の実装方法

 それでは実際に排他制御をどのように実装すればよいのか見ていきましょう。

時刻印アルゴリズム

 楽観的排他制御を行う際、「更新前」と「更新時」に対象データが変更されたかどうかをチェックするということはすでに述べました。

 では、「変更された」ことをどのようにして検出すればよいでしょうか?

 最も簡易な方法としては、対象データのすべての項目について、変更前と変更時の値を比較してチェックする方法があります。しかし、この方法は比較項目が多く、無駄が多いのでお勧めしません。

 そこで、どうするかというと、データ変更時に必ず値を更新する「時刻印(タイムスタンプ)」項目を追加し、このタイムスタンプを変更前、変更時で比較するようにします。この方法のことを「時刻印アルゴリズム」と呼びます。

 予約テーブルに時刻印アルゴリズムを適用するケースの例を以下に示します。

図4 時刻印アルゴリズム
図4 時刻印アルゴリズム

 時刻印アルゴリズムを用いることの利点は、比較項目が1つだけとなり実装がシンプルになることです。しかし、何よりもSQL Server、ASP.NET、そしてEntity Frameworkに実装をサポートする機能が備わっていることが大きな利点です。

時刻印アルゴリズムの実装

 では、実際に時刻印アルゴリズムを用いた楽観的排他制御の実装方法を見ていきましょう。

 予約登録機能に組み込んでいきます。

[1]POCO Entityにタイムスタンプを追加する

 Reservationエンティティクラスにタイムスタンプ項目を追加します。

リスト1 MRRS.Entity.Reservation.cs
public class Reservation
{
  public int ReservationId { get; set; }
  public int MeetingRoomId { get; set; }
  public DateTime ReserveDateFrom { get; set; }
  public DateTime ReserveDateTo { get; set; }
  public string Purpose { get; set; }
  public string Remarks { get; set; }

  [Timestamp] //(1)
  public byte[] Version { get; set; } //(2)

  
  public virtual MeetingRoom MeetingRoom { get; set; }
}
  • (1)タイムスタンプ列であることを示すため、System.ComponentModel.DataAnnotations.Timestamp属性を付ける。
    Timestamp属性が付いた項目は、SQL Server上でrowversion型の列として定義されます。
  • (2)rowversion型の列に対応する型であるbyte[]型として、タイムスタンプ項目「Version」を定義する。

 タイムスタンプ項目があると、EFは自動でその項目を使った楽観的排他制御を行うようになります。

rowversion型とは

 SQL Server 2005から登場した列の型で、レコードの更新時に自動的にカウントアップされる二進数のデータ型です。rowversion型を用いることで、開発者がコードによりタイムスタンプ列の更新を行う必要がなくなります。詳しくは以下のページを参照してください。
 

[2]Reservation.aspxに排他制御のための変更を加える

 排他制御を行うために、主にFormViewに変更を加えます。

リスト2 MRRS.Reservation.aspx
<asp:ObjectDataSource ID="ReservationsObjectDataSource" runat="server"
  DataObjectTypeName="MRRS.Entity.Reservation"
  TypeName="MRRS.BLL.ReservationLogic" SelectMethod="Find"
  UpdateMethod="Update" DeleteMethod="Delete"
  OnSelected="ReservationsObjectDataSource_Selected"
  ViewStateMode="Enabled">
  <SelectParameters>
    <asp:QueryStringParameter Name="reservationId"
      QueryStringField="reservationId" />
  </SelectParameters>
</asp:ObjectDataSource>
<!-- (1) -->
<asp:FormView ID="ReservationsFormView" runat="server"
  DataSourceID="ReservationsObjectDataSource" DefaultMode="Edit"
  DataKeyNames="ReservationId,Version"
  OnItemDeleted="ReservationsFormView_ItemDeleted"
  OnItemUpdated="ReservationsFormView_ItemUpdated"
  OnItemUpdating="ReservationsFormView_ItemUpdating"
  ViewStateMode="Enabled">  <!-- (2) -->
  • (1)FormView.DataKeyNamesプロパティに[1]で追加したタイムスタンプ列を追加する。
    追加しないと、対象データを取得した際のタイムスタンプ値が更新処理に引き渡されなくなります。
  • (2)FormViewのビューステートを有効にする。
    有効にしないと、更新前に再びデータ取得処理(ReservationLogic.Findメソッド)が実行されます。そのため、必ず最新のタイムスタンプを取得してから更新処理に行くため、排他制御でエラーになりません。

[3]Reservation.aspx.csに排他エラー時の処理を記述する

 排他エラーが発生するとSystem.Data.Entity.Infrastructure.DbUpdateConcurrencyExceptionが発生するので、本連載第3回『エラー処理をパターンにはめよう』を参考に適切に例外を処理し、排他エラーが発生したことをユーザーに通知します。

リスト3 MRRS.Reservation.aspx.cs
protected void ReservationsFormView_ItemUpdated(object sender, FormViewUpdatedEventArgs e)
{
  var ex = e.Exception;
  if (ex != null)
  {
    if (ex.InnerException is ReservationLogic.OverlapReservationException)
    {
      this.ShowErrorMessage("予約期間が重なる予約がすでに登録されています。");

      // 入力値を維持
      e.KeepInEditMode = true;

      // 例外を処理済みとマーク
      e.ExceptionHandled = true;
    }

    if (ex.InnerException is DbUpdateConcurrencyException)  //(1)
    {
      this.ShowErrorMessage("他のユーザーにより先に更新されました。もう一度一覧画面より選択してやり直してください。");  //(2)


      // 入力値を維持
      e.KeepInEditMode = true;

      // 例外を処理済みとマーク
      e.ExceptionHandled = true;
    }

    // 例外発生時は画面遷移しない
    return;
  }

  // 予約参照画面に戻る
  Response.Redirect("~/Reservations.aspx");
}
  • (1)FormView.ItemUpdatedイベントで発生した例外のInnerExceptionがDbUpdateConcurrencyExceptionであるか判定する。
  • (2)排他エラーである旨のエラーメッセージを表示する。

 同様にFormView.ItemDeletedイベントについても、排他エラー発生時の処理を実装します。

[4]MRRS.DAL.ReservationRepository.csで、削除処理を変更する

 これまでの削除処理は最初に削除対象データを取得し、そのデータを使ってIDbSet<T>.Removeメソッドを呼び出していました。

リスト4 変更前のMRRS.DAL.ReservationRepository.cs
public void Delete(Reservation reservation)
{
  var target = context.Reservations.Find(reservation.ReservationId);
  context.Reservations.Remove(target);
}

 このままでは削除時にVersion列の比較を行おうにも、必ず最新のデータを使ってしまうため、常に「変更されていない」と判定されてしまいます。

 そこで、次のように、変更処理と同じようにEntityStateだけを変更するよう、コードを修正します。

リスト5 変更後のMRRS.DAL.ReservationRepository.cs
public void Delete(Reservation reservation)
{
  context.Entry<Reservation>(reservation).State = EntityState.Deleted;  //(1)
}
  • (1)DbEntityEntry<T>.Stateプロパティに、EntityState.Deletedを設定することにより、「削除」とマークする。

次のページ
実行結果

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
実例で学ぶASP.NET Webフォーム業務アプリケーション開発のポイント連載記事一覧

もっと読む

この記事の著者

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

WINGSプロジェクト 高野 将(タカノ ショウ)

<個人紹介>新潟県長岡市在住の在宅リモートワークプログラマー。家事や育児、仕事の合間に長岡IT開発者勉強会(NDS)、Niigata.NET、TDDBCなどのコミュニティに関わったり、Web記事や書籍などの執筆を行ったりしている。著書に『アプリを作ろう! Visual C#入門 Visual C# 2017対応』(日経BP社、2017)など。<WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/6764 2012/09/25 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング