け〜くんこと K.Ktouth のだらだらした日常と突発的に作るプログラムや読み物とかの雑多サイト
昨日の日記で書いたとおり、以前アップした追加処理ではリレーションシップの両端が新規オブジェクトの場合に、それが正しく反映できないという問題がありました。
今回、その対応を含めた処理の修正コードを提示します。
(以下、修正した実装コード)
今回の修正は、エンティティ型およびコンテキスト型のイベントハンドラ、両方に及びます。
要するに「リレーションシップの両端が常に一意的である」ならOKな訳ですから、独自の追加処理(=Idの設定)を全ての追加エンティティに行った後にコンテキストへの再接続処理を行うようにすれば問題ありません。
具体的には以前アップした追加処理の最後の方の処理概要の4〜7番を、4とそれ以降の2段階に分けて行えばいいわけです。以前のコードでは一つのループでやっていました。
また、リレーションシップの変更はxxxxReferenceというプロパティを参照することで変更イベントをフックすることが出来ますが、これは通常の変更時でもAttach時にも発生するした上にその区別が付きません。
状況によってはこの区別が付いた方が良いと思われるので、そのための処理も追加します。
interface IEntityInsertHelper
{
bool InProcessing { get; set; }
void InsertItem(SqlCeConnection connection);
}
追加したプロパティは後に使用する再アタッチ処理の判別用プロパティです。
#region IEntityInsertHelper メンバ
bool IEntityInsertHelper.InProcessing { get; set; }
void IEntityInsertHelper.InsertItem(SqlCeConnection connection)
{
if (Id != 0) { return; }// TODO: 必要なら、ここに必須エンティティの追加処理を呼び出す
var command = connection.CreateCommand();
command.CommandText = "INSERT INTO AddressGroup(AreaId, Name, Deleted, Apartments, Format, InputFormat, OldId, Created)" +
" VALUES (@area, @name, @deleted, @ap, @format, @input, @old, @created)";command.Parameters.Add(new SqlCeParameter("area", this.Area.Id));
command.Parameters.Add(new SqlCeParameter("name", this.Name));
command.Parameters.Add(new SqlCeParameter("deleted", this.Deleted));
command.Parameters.Add(new SqlCeParameter("ap", this.Apartments));
command.Parameters.Add(FooEntities.CreateNullOrValue("format", this.Format));
command.Parameters.Add(FooEntities.CreateNullOrValue("input", this.InputFormat));
command.Parameters.Add(FooEntities.CreateNullOrValue("old", this.NewestInfo, x => x.Id));
command.Parameters.Add(new SqlCeParameter("created", this.Created.Date));command.ExecuteNonQuery();
this.Id = FooEntities.GetKey(connection, this);
}#endregion
コンストラクタ()
: base()
{
(this as IEntityInsertHelper).InProcessing = false;
}
エンティティ型での追加コードは先ほどの InProcessing プロパティの実装と初期化です。プロパティの実装を省略表記で記述しているため、初期化処理をコンストラクタで行っています。
また InsertItem メソッドの最初で再入判定処理を行っています。複数回呼び出しても大丈夫なようにキーの値が設定されているかを見て判断しています。
FooEntities() {
this.SavingChanges += ContextSavingChanges;
}private void ContextSavingChanges(object sender, EventArgs e)
{
// TODO: ここで適切な追加・変更オブジェクトの検証処理を行うSqlTransaction((c, t) =>
{
var stack = new List<IEntityInsertHelper>(
from ent in _Context.ObjectStateManager.GetObjectStateEntries(EntityState.Added)
where ent.Entity is IEntityInsertHelper
select ent.Entity as IEntityInsertHelper
);
foreach (var obj in stack) { obj.InsertItem(c); }
foreach (var obj in stack)
{
try
{
obj.InProcessing = true;
_Context.Detach(obj);
(obj as EntityObject).EntityKey = _Context.CreateEntityKey(typeof(XxxEntities).Name + "." + obj.GetType().Name, obj);
_Context.Attach(obj as IEntityWithKey);
}
finally { obj.InProcessing = false; }
}
});
}
イベントハンドラ、特にトランザクション処理の内部はほぼ総書き換えになっています。
一端 stack リストに追加対象オブジェクトをコピーしているのは、ObjectStateManager.GetObjectStateEntries の返値はオブジェクトの再アタッチをするたびにリストから無くなってしまうため、今回のようにループを複数回行うためには一端どこかに保持しておく必要があったからです。また、修正前のようにいちいち if 文を使って判定するのではなく、LINQ 文を使って見やすいコードに替えてみました。
再アタッチ処理に関しては対象オブジェクトに処理の最中であることを通知するために、新設した InProcessing プロパティを使っています。
ここで気になるのが、一対多のリレーションシップで繋がっている時に追加の順番が適切に並んでいるか、です。
上記の InsertItem メソッド例のコードで言えば、this.Area が新規オブジェクトの際に、this.Area.Id が適切な(0以外の)値になっているかという問題です。
デバッグ時のトレース情報を見る限りは、単純なリレーションシップの場合に ObjectStateManager.GetObjectStateEntries で得られる列挙値の並びは、このリレーションシップを考慮したものになっているようです。
複雑な連携だったり、しっかりとした保証をかけたい場合は connection.CreateCommand() の処理の前あたりで、キーを確定したいエンティティの InsertItem メソッドを先に読んでおくといいと思います。先ほど追加した再入時の適切なスルー処理はそのためのものです。
とりあえず、これでリレーションシップを考慮した追加処理になったと思います。
OVA『バカとテストと召喚獣 〜祭〜』上巻 [Blu-ray]
(メディアファクトリー)
新品 ¥ 4,409
中古 ¥ 3,680
OVA『バカとテストと召喚獣 〜祭〜』下巻 [Blu-ray]
(メディアファクトリー)
新品 ¥ 4,438
中古 ¥ 2,980
生徒会役員共DJCD「アニメ生徒会役員共が全部わかるラジオ、略して全ラ!...
(キングレコード)
新品 ¥ 2,253
中古 ¥ 1,850
WORKING!! 9 初回限定特装版 (SEコミックスプレミアム)
(高津 カリノ)
新品 ¥ 2,896
中古 ¥ 2,835
「声優グランプリ」公認!声優界<雀王>決定戦! <J-1グランプリ> V...
(オムニバス)
新品 ¥ 3,159
中古 ¥ 3,099
デッドライジング 2【CEROレーティング「Z」】
(カプコン)
新品 ¥ 2,740
中古 ¥ 1,019
スプリンターセル コンヴィクション
(ユービーアイ ソフト)
新品 ¥ 2,979
中古 ¥ 1,400
Xbox 360 250GB
(マイクロソフト)
中古 ¥ 25,600
学園黙示録 HIGHSCHOOL OF THE DEAD 7 オリジナル...
(佐藤 大輔)
新品 ¥ 9,800
中古 ¥ 4,879
学園黙示録 HIGHSCHOOL OF THE DEAD SECRET ...
(佐藤 大輔)
中古 ¥ 2,500
ソードアート・オンライン〈5〉ファントム・バレット (電撃文庫)
(川原 礫)
新品 ¥ 578
中古 ¥ 450
RPG W(・∀・)RLD6 ―ろーぷれ・わーるど― (富士見ファンタ...
(吉村 夜)
新品 ¥ 651
中古 ¥ 1
魔法少女リリカルなのはViVid (3)限定版 (角川コミックス・エース...
(藤真 拓哉)
新品 ¥ 1,680
中古 ¥ 1,280
魔法戦記リリカルなのはForce (3)限定版 (角川コミックス・エース...
(緋賀 ゆかり)
新品 ¥ 1,680
中古 ¥ 420
ダンジョンズ&ドラゴンズ第4版スターター・セット
(ジェームズ ワイアット)
新品 ¥ 4,980
中古 ¥ 4,311
アリアンロッド・サガ・リプレイ(5) 激闘のピースメイカー (富士見ド...
(菊池 たけし/F.E.A.R.)
新品 ¥ 693
中古 ¥ 190
アリアンロッド・サガ・コンチェルト 1 (電撃コミックス)
(佐々木 あかね)
新品 ¥ 599
中古 ¥ 90
堀さんと宮村くん(7)(ガンガンコミックスONLINE)
(HERO)
新品 ¥ 1,000
中古 ¥ 747
浅尾さんと倉田くん(3)(ガンガンコミックスONLINE)
(HERO)
新品 ¥ 690
中古 ¥ 376
7と嘘吐きオンライン―HERO個人作品集―(ガンガンコミックスONLIN...
(HERO)
新品 ¥ 710
中古 ¥ 169
ONE PIECE 65 (ジャンプコミックス)
(尾田 栄一郎)
NARUTO―ナルト― 59 (ジャンプコミックス)
(岸本 斉史)
ワンピース 海賊無双(通常版)(初回特典:オリジナルカスタムテーマ9種DLコード、ソーシャルゲーム専用レアフィギュア用コード同梱)
(バンダイナムコゲームス)
ドラゴンズドグマ(数量限定特典『バイオハザード6』体験版用DLコード同梱)
(カプコン)