いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

DevExpress奮闘記-029 (XPOでの非同期ローディング)(XPO, LoadAsync, XPCollection, UnitOfWork, CommitChangesAsync)


もうすぐ v2009.2 のリリースとなるのですが、先行して現在RC版がリリースされています。
CodeRush等のIDE Toolsの速度がめっちゃ早くなっていたりと何かといいことづくしなのですが
その中でも、個人的に気になる新規機能が以下の点です。
その他の点についてはv2009.2がリリースされたらまた記事書きたいと思います。

XPOにて非同期ロード・非同期コミットがサポートされるようになった。


内心、やっとサポートしてくれたって感じなのですが、やっぱり自前で実装するよりも
本家がサポートしてくれる方が嬉しいですね。


現状、ドキュメントにも記載されていませんが本番リリース時には記載されていることでしょう。


非同期操作を行うメソッドは、XPBaseCollectionとSession, UnitOfWorkにそれぞれ追加されています。


[XPBaseCollection]

  • LoadAsync


[Session]

  • GetObjectsAsync
  • CommitTransactionAsync


[UnitOfWork]

  • CommitChangesAsync


使い方はみんな同じなので、今回はLoadAsyncのサンプルを以下に記述します。
まず、マッピングするクラスの作成。

using System;
using DevExpress.Xpo;

namespace PersistObjects {

    public class Person : XPBaseObject {

        [Key]
        public int Id;
        public string Name;
        public int Age;
        public string Tel;

        public Person(Session session) : base(session) {
        }
    }
}


次にテストデータとデータベースを作成。
今回は、簡便に済ますためにAccessデータベースを利用します。
とりあえずデフォルトで10万件のレコードを作成しておきます。


using System;
using System.Windows.Forms;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using PersistObjects;

namespace InitializeData {

    public partial class MainForm : Form {

        const string FILE_NAME = @"c:\TestData.mdb";

        public MainForm() {
            InitializeComponent();
        }

        private void simpleButton1_Click(object sender, EventArgs e) {

            string connString = AccessConnectionProvider.GetConnectionString(FILE_NAME);

            UnitOfWork uow = new UnitOfWork();
            uow.ConnectionString = connString;
            uow.AutoCreateOption = AutoCreateOption.DatabaseAndSchema;
            uow.Connect();
            uow.ClearDatabase();

            calcEdit1.Enabled = false;
            simpleButton1.Enabled = false;

            MethodInvoker invoker = () => {calcEdit1.Enabled = true; simpleButton1.Enabled = true;};
            new Action<UnitOfWork>(CreateData).BeginInvoke(uow, (result) => { Invoke(invoker); }, null);
        }

        private void CreateData(UnitOfWork uow) {

            try {

                int dataCount = Convert.ToInt32(calcEdit1.EditValue);
                for (int i = 0; i < dataCount; i++) {
                    Person p = new Person(uow);

                    p.Id = i;
                    p.Name = string.Format("gsf_zero{0}", i);
                    p.Age = i;
                    p.Tel = string.Format("xxx-xxxx-{0}", i);
                }

                uow.CommitChanges();

            } catch(Exception ex) {
                MessageBox.Show(ex.ToString());
            }            
        }
    }

}


そして、非同期ロードをおこなってみます。
やり方は以下のサンプルを見ていただいたら分かると思います。
LoadAsyncメソッドを呼ぶ際にコールバックが必要な際は、AsyncLoadObjectsCallbackデリゲートを
設定します。

using System;
using System.Collections;
using System.Windows.Forms;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using DevExpress.Xpo.Helpers;
using PersistObjects;

namespace AsyncLoad {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void simpleButton1_Click(object sender, EventArgs e) {

            simpleButton1.Visible = false;
            labelControl1.Visible = true;
            gridControl1.Enabled = false;

            UnitOfWork uow = new UnitOfWork();
            uow.ConnectionString = AccessConnectionProvider.GetConnectionString(@"c:\TestData.mdb");
            uow.AutoCreateOption = AutoCreateOption.SchemaAlreadyExists;
            uow.Connect();

            XPCollection<Person> xpCol = new XPCollection<Person>(uow);
            xpCol.LoadAsync(AsyncLoadCallback);

            gridControl1.DataSource = xpCol;
        }

        private void AsyncLoadCallback(ICollection[] result, Exception ex) {
            labelControl1.Visible = false;
            gridControl1.Enabled = true;
        }
    }
}

実行してみた感じはこんな感じになります。

[表示時]

[ロード中]

[ロード完了]

コールバックメソッドは、ロードが完了した時点で呼ばれます。
引数の一つ目には、読み込んだデータが設定されています。2つ目の引数には
エラーが発生した場合に該当する例外オブジェクトが設定されます。


今回のサンプルについては、ソリューション毎以下の場所にアップしてあります。
実際に動かして見る型は、プロジェクト名に番号振ってあるのでその順に起動して
確かめてみてください。
http://sites.google.com/site/gsfzero1/Home/XPOAsyncSamples.LZH?attredirects=0


[自分用の補足]
ついでにもう一つ自分のメモ書きとして記述。
上のフォームのAssembly.Load版

using System;
using System.Collections;
using System.Windows.Forms;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using DevExpress.Xpo.Helpers;
using System.Reflection;

namespace AsyncLoad {

    public partial class Form1 : Form {

        public Form1() {
            InitializeComponent();
        }

        private void simpleButton1_Click(object sender, EventArgs e) {

            simpleButton1.Visible = false;
            labelControl1.Visible = true;
            gridControl1.Enabled = false;

            UnitOfWork uow = new UnitOfWork();
            uow.ConnectionString = AccessConnectionProvider.GetConnectionString(@"c:\TestData.mdb");
            uow.AutoCreateOption = AutoCreateOption.SchemaAlreadyExists;
            uow.Connect();

            Assembly.Load("PersistObjects");

            XPCollection xpCol = new XPCollection(uow, uow.GetClassInfo("PersistObjects", "PersistObjects.Person"));
            xpCol.LoadAsync(AsyncLoadCallback);

            gridControl1.DataSource = xpCol;
        }

        private void AsyncLoadCallback(ICollection[] result, Exception ex) {
            labelControl1.Visible = false;
            gridControl1.Enabled = true;
        }
    }
}


ついでに、動的型定義(Emit)版。

using System;
using System.Collections;
using System.Data.Common;
using System.Windows.Forms;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using DevExpress.Xpo.Metadata;
using Gsf.Lib.Data.Common;
using Gsf.Lib.Xpo;

namespace AsyncLoad {

    public partial class Form1 : Form {

        public Form1() {
            InitializeComponent();
        }

        private void simpleButton1_Click(object sender, EventArgs e) {

            simpleButton1.Visible = false;
            labelControl1.Visible = true;
            gridControl1.Enabled = false;

            UnitOfWork uow = new UnitOfWork();
            uow.ConnectionString = OracleConnectionProvider.GetConnectionString("xxxx", "xxxxx", "xxxx");
            uow.AutoCreateOption = AutoCreateOption.SchemaAlreadyExists;
            uow.Connect();

            XpoTypeGenerator generator = XpoTypeGeneratorFactory.GetTypeGenerator(Database.Oracle);
            generator.Connection = (DbConnection) uow.Connection;

            Type type = (Type) generator.Generate("xxxxxx");
            XPClassInfo classInfo = uow.GetClassInfo(generator.CurrentAssemblyName.FullName, type.FullName);

            XPCollection xpCol = new XPCollection(uow, classInfo);
            xpCol.LoadAsync(AsyncLoadCallback);
            
            gridControl1.DataSource = xpCol;
        }

        private void AsyncLoadCallback(ICollection[] result, Exception ex) {
            labelControl1.Visible = false;
            gridControl1.Enabled = true;
        }
    }
}