2009年10月26日月曜日

LinqToSqlでの開発の注意点 その1

LinqToSqlは.net framework3.5より導入されたORM(Object-relational mapping)だ。LinqToSql以前はORMといえばNHibernateなどが主流だったと思う。いまだにいくつかの点でNHibernateに譲るけれど(2nd Level Cacheやクエリーベースの取得など)、LinqToSqlのほうがよりシンプルな印象を持った。私見だがLinqToSqlを使用してのプロジェクト開発は、ORMを使用しないプロジェクトに比して段違いに開発スピードが速い。これから複数回に分けてLinqToSqlの注意点について解説していきたいと思う。

まず一番初めに直面するであろう問題はデータの更新時に訪れる。プロジェクトへdbmlファイルを追加し、必要なTableも追加し、データを取得、挿入までトントンと進むと思われるが、dbmlへ何も対処を施していないと挿入したデータを更新する際に下記の例外に直面する。

An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy
(注意:実際に発生する例外は若干違うかもしれない。今回は実地に試す環境がないので思い出しながら解説しています)

ソースコードはこのようなものを想定している。

void Update(Cilp clip)
{
 var db = new MyClipDataContext();
 db.Clips.Attach(clip, true);
 db.SubmitChanges();
}

この例外は並列処理時の上書き更新を防ぐための処理が初期状態でdbmlに施されているために発生する。これを避けるためには各テーブルにTimeStamp列を追加し、更新を下記のように変更する必要がある。

void Update(Cilp clip)
{
 var db = new MyClipDataContext();
 var dbClip = db.Clips.Single(x => x.Id==clip.Id);
 dbClip.Value = clip.Value;
 db.SubmitChanges();
}

これでひとまず例外が発生することは無くなった。ただ上記の例では上書き更新を防いでいるとはいえないだろう。実際には下記のように使うのが正しい。ちなみにこのソースコードは一つ目の例と同じだ。ただClipsテーブル上にTimeStamp列が追加されているものとし、さらに(ここが重要なのだが)引数clipは新規にnewしたものではなく、DBから取得したデータをSerialize, Deserializeしたものとする。

void Update(Cilp clip)
{
 var db = new MyClipDataContext();
 db.Clips.Attach(clip, true);
 db.SubmitChanges();
}

これにより、データ取得時のTimeStampを損なうことなく正確な上書き防止処理が期待できる。

ただ、上書き防止などを必要としない処理を行っている場合は、このような実装を行う必要はなく、下記のようにdbml上で各列ごとにUpdateCheckをNeverに設定すればよい。



これでTimeStampなどを気にすることなく更新処理が行えるようになる。ここで注意しなければならないのは、SQL Server上でテーブルを更新した場合、その更新をdbmlに反映するには再度テーブルをdbmlにD&Dする必要がある(信じられないことにVS2008にはそれ以外にSQL Server上の変更をdbmlへ反映する手段が無い)。このときにUpdateCheckをNeverに変更する処理を忘れずに行うようにしよう。

0 件のコメント:

コメントを投稿