久しぶりに 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” を更新することで行いました。