読者です 読者をやめる 読者になる 読者になる

いろいろ備忘録日記

主に .NET 絡みのメモを公開しています。

DevExpress奮闘記-096 (XPOとIdentityMap, リロード時、キャンセル時の注意点, XPLiteObject, UnitOfWork, DropIdentityMap, ReloadChangedObjects, XPCollection.Reload, OptimisticLockField)

C# DevExpress


XPOを利用していて、リロード処理を記述する際によく引っかかる点をメモメモ。
XPOを扱う上で重要な事なので結構長文です。文字ばっかりですw


XPOを使っていてUI側などでデータを変更しコミットした場合、当然処理の最後にはデータをリロードします。
その場合、大抵UI側へのバインディングにはXPCollectionを利用していると思います。
その際、エンティティクラスの親クラスとしてXPLiteObjectを利用している場合単純に

XPCollection.Reload();

としてもデータがリロードされません。結構はまりますコレ。


XPLiteObject以外(XPObject, XPCustomObject, XPBaseObject)を利用している場合
普通にリロードできます。


で、何故このようにXPLiteObjectだけがリロードできないのかには、ちゃんと理由があって
以下のKBに記述されています。


上記のKBに記述されていますが、XPOのセッションは内部でIdentityMapという
キャッシュに似た構造を持っています。IdentityMapは、有名なP of EAA
提言されているパターンです。


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();
    }
}

================================
過去の記事については、以下のページからご参照下さい。