Entity Framework 4.1 での同時更新対応

久しぶりに C# の話題で。

ASP.NET MVC と Entity Framework を利用した開発でのデータ更新の衝突への対応法が Handling Concurrency with the Entity Framework in an ASP.NET MVC Application (7 of 10) (Microsoft ASP.NET) に書かれていますが、”連番になるユニークな値を取得したい” ような場合は、どうすればいいのな?ということで、試してみました。

試すにあたって想定したのは、画像のアップロードの際にファイル名を数字連番にするものです。

ということで、モデルは次のとおり。

using System.ComponentModel.DataAnnotations;
namespace Board.Models
{
    public class Upload
    {
        public int UploadId { get; set; }
        public int ファイルNo { get; set; }
        [Timestamp]
        public Byte[] Timestamp { get; set; }
    }
}

[Timestamp]属性を付けることで、Entity Framework がSaveChanges() メソッド実行時に衝突の検出を行うようになります。

一応、コンテキスト・クラスと接続文字列も。

namespace Board.Models
{
    public class BoardContext : DbContext
    {
        public DbSet<Upload> Uploads { get; set; }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings>
      <add name="BoardContext"
           connectionString="data source=|DataDirectory|Board.sdf" providerName="System.Data.SqlServerCe.4.0"/>
      <add name="ApplicationServices"
         connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true"
         providerName="System.Data.SqlClient" />
  </connectionStrings>
~

で、リポジトリ部分です。

using System.Data.Entity.Infrastructure;

namespace Board.Models
{
    public class BbsRepository : IBbsRepository
    {
        private BoardContext _db = new BoardContext();

~

        public int GetPictureNo(int key)
        {
            var currentNo = 0;
            var isOk = false;
            var no = _db.Uploads.SingleOrDefault(d => d.UploadId == key);
            while (!isOk)
            {
                currentNo = no.ファイルNo;
                if (currentNo == int.MaxValue)
                    throw new OverflowException("アップロードされたファイル数が制限値の上限になっています。");
                no.ファイルNo++;
                try
                {
                    _db.SaveChanges();
                    isOk = true;
                }
                catch (DbUpdateConcurrencyException ex)
                {   // クライアントによる最後の読み取り以降にデータベースの値が更新されたため、更新が失敗した場合
                    _db.Entry(no).Reload();
                    isOk = false;
                }
            }

            return currentNo;
        }
    }
}

GetPictureNo メソッドの引数は、Upload テーブルからレコードを取得するためのキーになります。
衝突を検知すると、Entity Framework から “DbUpdateConcurrencyException” が投げられるので、それをキャッチします(ネームスペースは “System.Data.Entity.Infrastructure”)。
衝突の検出は、DB 上の項目 “Timestamp” を利用して行われます(SQL Server 側で 更新が行われるたびに、カウントアップを行うので、DB 上の値と更新前の値を比較しています)。
で、この場合、衝突を検出したら、DB から読みなおして再度番号を取得したいわけです(このロジックだと、DB から連番を取得して、+1 したものを書き戻すことで次の連番を用意すると共に、衝突検出をしています)。この読みなおしのために、 DbEntityEntry.Reload メソッドを利用しています。

動作確認は、”currentNo = no.ファイルNo;” のところにブレークポイントを設定して、止まっている間に手動で DB 上の “ファイルNo” を更新することで行いました。


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です