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

いろいろ備忘録日記

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

DevExpress奮闘記-078 (InstantFeedback Modeを試してみた)(LinqInstantFeedbackSource, XPInstantFeedbackSource, IQueryable, FindPanelのバグ)

C# DevExpress


結構前に発表されていて、なかなか試す機会がなかったInstantFeedback Modeを試してみました。
InstantFeedback UIについては、こちらを参照願います。


簡単に言うと、XtraGridなどでサーバーモードで読み込み時に、エンドユーザにローディング中なのが
分かるようにフィードバック用のUIを表示する機能です。
WinForms版(XtraGrid)だけでなく、WPF, Silverlight側(DXGrid)にも実装されています。


InstantFeedback Modeはサーバーモードで利用する事になります。
その際、特定のデータソースを設定する必要があります。


指定できるデータソースは以下の2つです。

  • LinqInstantFeedbackSource
  • XPInstantFeedbackSource


LinqInstantFeedbackSourceは、IQueryableを受け付けるデータソースです。
以下の場合などで使えます。

  • Linq To SQL
  • ADO.NET Entity Framework
  • 特定のコレクションをIQueryableに変換したもの


実際のデータソースは以下のようにしてイベントをハンドルして設定します。

        lifLinqToSQL.KeyExpression     = "OID";
        lifLinqToSQL.GetQueryable     += lifLinqToSQL_GetQueryable;
        lifLinqToSQL.DismissQueryable += lifLinqToSQL_DismissQueryable;
            
        void lifLinqToSQL_DismissQueryable(object sender, GetQueryableEventArgs e)
        {
            SampleLinqToSQL.DataClasses1DataContext context = e.Tag as SampleLinqToSQL.DataClasses1DataContext;
            context.Dispose();
        }

        void lifLinqToSQL_GetQueryable(object sender, GetQueryableEventArgs e)
        {
            SampleLinqToSQL.DataClasses1DataContext context = new SampleLinqToSQL.DataClasses1DataContext();
            e.QueryableSource = context.GetTable<SampleLinqToSQL.ServerSideGridTest>();
            e.Tag = context;
        }

GetQueryableイベントにて、データソースを設定し、DismissQueryableイベントにて破棄します。


XPInstantFeedbackSourceは、XPO専門のデータソースです。
こちらも、イベントをハンドルして設定します。

        xifDataSource.ObjectType = typeof(ServerModeGridProjects.ServerSideGridTest);
        xifDataSource.ResolveSession += xifDataSource_ResolveSession;
        xifDataSource.DismissSession += xifDataSource_DismissSession;
            
        void xifDataSource_ResolveSession(object sender, ResolveSessionEventArgs e)
        {
            Session session = new Session();
            session.ConnectionString = _connectionString;
            session.Connect();
            e.Session = session;
        }

        void xifDataSource_DismissSession(object sender, ResolveSessionEventArgs e)
        {
            IDisposable session = e.Session as IDisposable;
            if (session != null)
            {
                session.Dispose();
            }
        }

ResolveSessionイベントにて、セッションを設定し、DismissSessionイベントにてセッションを破棄します。


以下サンプルです。
以下のサンプルでは、以下のデータソースを設定してInstantFeedback Modeを
試しています。


ついでに、サーバーモード無しで通常通りデータソースも設定しています。
利用しているデータベースは、DemoCenterに付属しているサーバーモード用テーブルを利用しています。

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using System.Windows.Forms;
using DevExpress.Data.Linq;
using DevExpress.Xpo;
using DevExpress.XtraGrid.Views.Grid;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        const string SQL_QUERY = @"SELECT * FROM ServerSideGridTest";
        string _connectionString;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
            builder.DataSource         = @".\SQLEXPRESS";
            builder.InitialCatalog     = @"ServerModeGridProjects";
            builder.IntegratedSecurity = true;

            _connectionString = builder.ToString();

            xifDataSource.ObjectType = typeof(ServerModeGridProjects.ServerSideGridTest);
            xifDataSource.ResolveSession += xifDataSource_ResolveSession;
            xifDataSource.DismissSession += xifDataSource_DismissSession;

            lifList.KeyExpression     = "OID";
            lifList.GetQueryable     += lifList_GetQueryable;
            lifList.DismissQueryable += lifList_DismissQueryable;

            lifLinqToSQL.KeyExpression     = "OID";
            lifLinqToSQL.GetQueryable     += lifLinqToSQL_GetQueryable;
            lifLinqToSQL.DismissQueryable += lifLinqToSQL_DismissQueryable;

            lifEntityFramework.KeyExpression     = "OID";
            lifEntityFramework.GetQueryable     += lifEntityFramework_GetQueryable;
            lifEntityFramework.DismissQueryable += lifEntityFramework_DismissQueryable;

            (grdMain.MainView as GridView).ShowFindPanel();
        }

        private void btnServerModeList_Click(object sender, EventArgs e)
        {
            grdMain.DataSource = null;
            grdMain.DataSource = lifList;
        }

        private void btnServerModeLinqToSQL_Click(object sender, EventArgs e)
        {
            grdMain.DataSource = null;
            grdMain.DataSource = lifLinqToSQL;
        }

        private void btnServerModeEntityFramework_Click(object sender, EventArgs e)
        {
            grdMain.DataSource = null;
            grdMain.DataSource = lifEntityFramework;
        }

        private void btnServerModeXPO_Click(object sender, EventArgs e)
        {
            grdMain.DataSource = null;
            grdMain.DataSource = xifDataSource;
        }

        private void btnNotServerModeList_Click(object sender, EventArgs e)
        {
            grdMain.DataSource = null;
            grdMain.DataSource = GetNormalModeDataSource();
        }

        #region Entity Framework
        void lifEntityFramework_DismissQueryable(object sender, GetQueryableEventArgs e)
        {
            ServerModeGridProjectsEntities1 entities = e.Tag as ServerModeGridProjectsEntities1;
            entities.Dispose();
        }

        void lifEntityFramework_GetQueryable(object sender, GetQueryableEventArgs e)
        {
            ServerModeGridProjectsEntities1 entities = new ServerModeGridProjectsEntities1();
            e.QueryableSource = entities.ServerSideGridTest;
            e.Tag = entities;
        }
        #endregion

        #region Linq To SQL
        void lifLinqToSQL_DismissQueryable(object sender, GetQueryableEventArgs e)
        {
            SampleLinqToSQL.DataClasses1DataContext context = e.Tag as SampleLinqToSQL.DataClasses1DataContext;
            context.Dispose();
        }

        void lifLinqToSQL_GetQueryable(object sender, GetQueryableEventArgs e)
        {
            SampleLinqToSQL.DataClasses1DataContext context = new SampleLinqToSQL.DataClasses1DataContext();
            e.QueryableSource = context.GetTable<SampleLinqToSQL.ServerSideGridTest>();
            e.Tag = context;
        }
        #endregion

        #region List<T>
        void lifList_DismissQueryable(object sender, GetQueryableEventArgs e)
        {
            // nop.
        }

        void lifList_GetQueryable(object sender, GetQueryableEventArgs e)
        {
            using (SqlConnection conn = new SqlConnection(_connectionString))
            {
                conn.Open();

                SqlCommand command = conn.CreateCommand();
                command.CommandText = SQL_QUERY;

                SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);

                List<Model> models = new List<Model>();
                foreach (DbDataRecord record in reader)
                {
                    models.Add(new Model { OID = record.GetInt32(0), Subject = record.GetString(1), From = record.GetString(2) });
                }

                e.QueryableSource = models.AsQueryable();
            }
        }
        #endregion

        #region List<T> not ServerMode
        List<Model> GetNormalModeDataSource()
        {
            using (SqlConnection conn = new SqlConnection(_connectionString))
            {
                conn.Open();

                SqlCommand command = conn.CreateCommand();
                command.CommandText = SQL_QUERY;

                List<Model> models = new List<Model>();
                using (SqlDataReader reader = command.ExecuteReader())
                {
                    foreach (DbDataRecord record in reader)
                    {
                        models.Add(new Model { OID = record.GetInt32(0), Subject = record.GetString(1), From = record.GetString(2) });
                    }
                }

                return models;
            }
        }
        #endregion

        #region XPO
        void xifDataSource_ResolveSession(object sender, ResolveSessionEventArgs e)
        {
            Session session = new Session();
            session.ConnectionString = _connectionString;
            session.Connect();
            e.Session = session;
        }

        void xifDataSource_DismissSession(object sender, ResolveSessionEventArgs e)
        {
            IDisposable session = e.Session as IDisposable;
            if (session != null)
            {
                session.Dispose();
            }
        }
        #endregion
    }

    class Model
    {
        public int    OID     { get; set; }
        public string Subject { get; set; }
        public string From    { get; set; }
    }
}


実行すると、以下の画面が表示されます。

どれかのボタンを押下すると、データソースが設定されます。
必要な分のデータソースを取得するまでの間、グリッドのシートコーナー(左上の隅)に
ローディングアニメーションが表示されます。(Instant Feedback UI)


ついでに、FindPanelを表示して検索できるかどうかも確認してみました。


で、ここでListをIQueryableに変換して設定したものを
FindPanelで検索するとうまく検索できないことが分かりました。
設定方法がまずいのかなと思っていろいろ調整してみたのですが
何度やっても、うまくいかないのでDevExpressに報告したらバグ仕様との事です。
(追記:ドキュメント上に記載がないので、記載してくれるようDevExpress側に頼んだところ、次のバージョンのドキュメントで追記されるとのことです。)


そのうち修正されることを期待します。
(まあ、ListをIQueryableに変換して設定することはあまり無いと思いますが・・・)


サンプルを以下の場所にアップしました。
試してみたい方はどうぞ。

2011/07/05 追記:
ドキュメントの「Find Panel」のページを見てみると、以下の注意事項が追記されていました。
(http://documentation.devexpress.com/#WindowsForms/CustomDocument8869)

Searches performed using a Find Panel are case insensitive.
Searches via the Find Panel for case-sensitive data sources in server mode are not supported. The Find Panel always converts a search string to lower-case before searching.


================================

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

いろいろ備忘録日記まとめ

http://sites.google.com/site/gsfzero1/