XPOを利用していて、リロード処理を記述する際によく引っかかる点をメモメモ。
XPOを扱う上で重要な事なので結構長文です。文字ばっかりですw
XPOを使っていてUI側などでデータを変更しコミットした場合、当然処理の最後にはデータをリロードします。
その場合、大抵UI側へのバインディングにはXPCollectionを利用していると思います。
その際、エンティティクラスの親クラスとしてXPLiteObjectを利用している場合単純に
XPCollection.Reload();
としてもデータがリロードされません。結構はまりますコレ。
XPLiteObject以外(XPObject, XPCustomObject, XPBaseObject)を利用している場合
普通にリロードできます。
で、何故このようにXPLiteObjectだけがリロードできないのかには、ちゃんと理由があって
以下のKBに記述されています。
- How XPO reloads objects and collections
上記のKBに記述されていますが、XPOのセッションは内部でIdentityMapという
キャッシュに似た構造を持っています。IdentityMapは、有名なP of EAAで
提言されているパターンです。
- Identity Map
XPOは、データを扱う際、常にこのIdentityMapを見ています。
対象となるデータがIdentityMapに存在する場合、それを返し、存在しない場合
データストアから取得します。
当然、データは外部で勝手に更新されたりします。
その際、XPOは自身が持つIdentityMapとデータストアのデータを比較し
新たに取得するべきかどうかを決定します。
その時に利用されるのが
OptimisticLockField
となります。このフィールドの値を比較して更新されている場合
データがリロードされます。更新されていない場合はそのままです。
XPLiteObject以外の基底クラスは、OptimisticLockFieldが
デフォルトで有効となっているので、問題なくデータがリロードされます。
XPLiteObjectは、OptimisticLockFieldをもっていないので
XPOは、データが外部で更新されているかどうかが分かりません。
なので、常にIdentityMapのデータを返します。
これがリロードできない原因です。
ちなみに、XPCollection.Reloadは実際にデータをリロードするのではなく
XPOに対してデータをリロードするようにマークするだけです。OptimisticLockField
を持っていない場合、いくらリロードマークをつけても更新されているかがXPOには分からないので
結局IdentityMapのデータが取得され直すだけです。
で、ここまでウダウダと記述しましたが、解決策としては
IdentityMapをクリアしてしまえばいい。
となります。IdentityMapを強制クリアするには以下のメソッドを
利用します。
Session.DropIdentityMap();
これでIdentityMapがクリアされます。その後リロードすると
XPLiteObjectをつかっていてもデータがちゃんとリロードできます。
session.DropIdentityMap();
xpCol.Reload();
また、リロードとは別の話題ですが変更操作のキャンセル(取り消し)を行いたい場合も
あります。このやり方もちょっとコツがあって、Sessionを利用しているかUnitOfWorkを
利用しているかでやり方がちょっと違います。
Sessionを利用している場合、一発で戻すことが出来ません。
以下のように、更新対象となっているデータのリストを取得して一つずつ
Reloadさせていきます。
foreach (var saveData in session.GetObjectsToSave()) { saveData.Reload(); }
UnitOfWorkを利用している場合、以下の方法で一発で戻せます。通常Sessionよりも
UnitOfWorkを利用している方が多いので、こちらの方がよく利用します。
uow.ReloadChangedObjects();
ただし、この場合でも新規に追加した行や削除した行は戻らないので
そのような行が存在する場合は、以下のようにReloadも読んでおきます。
uow.ReloadChangedObjects(); xpCol.Reload();
以下、サンプル。まずはXPObjectを利用している場合です。
using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using DevExpress.Xpo; using DevExpress.XtraGrid.Views.Grid; namespace Reload_XPObject { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { grdMain.DataSource = new XPCollection<User>(new UnitOfWork()); } // 保存ボタン private void btnSave_Click(object sender, EventArgs e) { XPCollection<User> xpCol = grdMain.DataSource as XPCollection<User>; UnitOfWork uow = xpCol.Session as UnitOfWork; uow.CommitChanges(); btnReload.PerformClick(); } // 取り消しボタン private void btnRevert_Click(object sender, EventArgs e) { XPCollection<User> xpCol = grdMain.DataSource as XPCollection<User>; UnitOfWork uow = xpCol.Session as UnitOfWork; uow.ReloadChangedObjects(); xpCol.Reload(); } // リロードボタン private void btnReload_Click(object sender, EventArgs e) { XPCollection<User> xpCol = grdMain.DataSource as XPCollection<User>; // 永続クラスの親クラスがXPObject (OptimisticLockFieldを持つタイプ)の場合 // IdentityMapをクリアしなくても、リロードが出来る。 //uow.DropIdentityMap(); xpCol.Reload(); } } }
次に、XPLiteObjectを利用している場合
using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; using DevExpress.Xpo; using DevExpress.XtraEditors; using DevExpress.XtraGrid.Views.Grid; namespace Reload_XPLiteObject { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { grdMain.DataSource = new XPCollection<Products>(new UnitOfWork()); } // 保存ボタン private void btnSave_Click(object sender, EventArgs e) { var xpCol = grdMain.DataSource as XPCollection<Products>; var uow = xpCol.Session as UnitOfWork; uow.CommitChanges(); btnReload.PerformClick(); } // 取り消しボタン private void btnRevert_Click(object sender, EventArgs e) { var xpCol = grdMain.DataSource as XPCollection<Products>; var uow = xpCol.Session as UnitOfWork; uow.ReloadChangedObjects(); xpCol.Reload(); } // リロードボタン private void btnReload_Click(object sender, EventArgs e) { var xpCol = grdMain.DataSource as XPCollection<Products>; var uow = xpCol.Session as UnitOfWork; // // XPLiteObjectを利用している場合OptimisticLockFieldが // 存在しないので、そのままリロードしてもデータは更新されない。 // まず、IdentityMapをクリアした後、リロードを行う。 // uow.DropIdentityMap(); xpCol.Reload(); } }
================================
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ