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

いろいろ備忘録日記

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

DevExpress奮闘記-067 (XPOのDirect SQL QueriesとLinq To XPOについて)(v2010 vol2, Linq To XPO, Direct SQL, ExecuteQuery, GetObjectsFromQuery)

C# DevExpress


v2010 vol.2より、XPOにSQLを直接指定して実行する機能 (Direct SQL)が追加されました。
SessionもしくはUnitOfWorkより以下のメソッドを実行することにより、実行結果が取得できます。

  • ExecuteQuery
  • ExecuteScaler
  • ExecuteNonQuery


ADO.NETのメソッドと同じ名前なので覚えやすいですね。


ExecuteQueryメソッドは、SelectedDataオブジェクトを戻り値として返します。
SelectedDataオブジェクトは以下の構造になっています。


すでにUnitOfWorkオブジェクトをuowという名前で構築しているとします。

// まずSelectedDataオブジェクトを取得.
SelectedData queryResult = uow.ExecuteQuery(GetQuery());

//
// SelectedData.ResultSetプロパティよりSelectStatementResult[]が取得できる。
// 通常は要素数が1つとなっている。MARSのように複数クエリを一括で発行している場合は
// その分の結果が入っている(はず)
//
SelectStatementResult[] statementResults = queryResult.ResultSet;
SelectStatementResult   statementResult  = statementResult[0];

//
// SelectStatementResultオブジェクトはRowsプロパティをもっています。
// これが行データとなります。行データはSelectStatementResultRowオブジェクト
// となっています。
//
SelectStatementResultRow[] rows = statementResult.Rows;
SelectStatementResultRow   row  = rows[0];

//
// SelectStatementResultRowオブジェクトは、Valuesプロパティを持っています。
// 戻り値はobjectの配列となっており、この中にSQLのSELECT句で指定した順序で
// データが設定されています。
//
model.ProductId   = Convert.ToInt32(row.Values[0]);
model.ProductName = Convert.ToString(row.Values[1]);


まとめると、ExecuteQueryを発行した後は

ExecuteQuery -> SelectedData -> SelectStatementResult[ ] -> SelectStatementResultRow[ ] -> object[ ]

の流れでデータを取得していきます。


実際には、上のような冗長な書き方はあまりせずに以下のように書くことが多いのではないでしょうか。

var query = from   row in uow.ExecuteQuery(GetQuery()).ResultSet.First().Rows
            let    values = row.Values
            select new GridModel
                   {
                       ProductId   = Convert.ToInt32(values[0]),
                       ProductName = Convert.ToString(values[1])
                   };


また、v2010 vol.2より

XPDataView

クラスが追加されています。このクラスはXPCollection, XPViewのSelectedData版みたいなものです。
XPDataViewを利用すると先ほどのデータ取得部分が以下のようになります。

XPDataView xpDataView = new XPDataView(uow.Dictionary, uow.GetClassInfo<GridModel>());
xpDataView.LoadData(uow.ExecuteQuery(GetQuery()));


あとはこれをDataSourceとして追加すれば出来上がりです。
ExecuteScalerとExecuteNonQueryメソッドについては割愛します。
ADO.NETと同じ感覚で利用できます。


また、ExecuteQueryの他に以下のメソッドも追加されています。

  • GetObjectsFromQuery
  • GetObjectsFromQuery


このメソッドは、SQLを発行して、その結果を指定したPersistentオブジェクトの
各プロパティに設定して、ICollectionの形式で返してくれます。
iBatis (MyBatis)のような感じです。


以下のようになります。

ICollection<GridModel> result = uow.GetObjectsFromQuery<GridModel>(GetQuery());

iBatisと同じく、SQLのSELECT句の列名称とクラスのプロパティの名称が同じ場合は
自動的に同じプロパティ名のところに結果を設定してくれます。
プロパティ名が異なっている場合は、別途設定が必要です。


詳細な情報はDevExpressの本家ブログの以下の記事を参照ください。


以下、各サンプルです。
[ExecuteQueryのサンプル]

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;

namespace XPODirectSqlSample
{
    public partial class Form1 : Form
    {
        static readonly string SERVER   = @".\SQLEXPRESS";
        static readonly string DATABASE = @"Northwind";

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //
            // データ接続レイヤーを設定.
            //   本来この設定はエントリーポイントで行う方がよい.
            //
            XpoDefault.DataLayer = 
                XpoDefault.GetDataLayer(
                    MSSqlConnectionProvider.GetConnectionString(SERVER, DATABASE),
                    AutoCreateOption.SchemaAlreadyExists
                );

            //
            // SQLをXPOで直接指定してデータを取得する.
            //
            // ExecuteQueryメソッドを実行した結果は以下のような構造となっている。
            //
            // [構造]
            //    ExecuteQuery
            //      ==> SelectedData
            //            ==> SelectStatementResult[]
            //                  ==> SelectStatementResultRow[]
            //                        ==> SelectStatementResultRow.Values
            //
            var query = GetQuery();
            using (var uow = new UnitOfWork())
            {
                SelectedData queryResult = uow.ExecuteQuery(query);

                SelectStatementResult[]    statementResults = queryResult.ResultSet;
                SelectStatementResult      statementResult  = statementResults.First();
                SelectStatementResultRow[] rows             = statementResult.Rows;

                List<GridModel> ds = new List<GridModel>();
                foreach (SelectStatementResultRow row in rows)
                {
                    GridModel model = new GridModel();

                    object[] values = row.Values;
                    model.ProductId    = Convert.ToInt32(values[0]);
                    model.ProductName  = Convert.ToString(values[1]);
                    model.CategoryName = Convert.ToString(values[2]);
                    model.CompanyName  = Convert.ToString(values[3]);

                    ds.Add(model);
                }

                gridControl1.DataSource = ds;

                ///////////////////////////////////////////////////
                //
                // 上は分かりやすいように冗長に記述しているが
                // 以下のようにも記述できる.
                //
                //var dsQuery = from   row in queryResult.ResultSet.First().Rows
                //              let    values = row.Values
                //              select new GridModel 
                //                     {
                //                         ProductId    = Convert.ToInt32(values[0]),
                //                         ProductName  = Convert.ToString(values[1]),
                //                         CategoryName = Convert.ToString(values[2]),
                //                         CompanyName  = Convert.ToString(values[3])
                //                     };
                
                //gridControl1.DataSource = dsQuery.ToList();
            }
        }

        private string GetQuery()
        {
            string query = string.Empty;

            query += " SELECT ";
            query += "      p.ProductID ";
            query += "     ,p.ProductName ";
            query += "     ,c.CategoryName ";
            query += "     ,s.CompanyName ";
            query += " FROM ";
            query += "     dbo.Products p ";
            query += "         INNER JOIN dbo.Categories c ";
            query += "         ON ";
            query += "             p.CategoryID = c.CategoryID ";
            query += "         INNER JOIN dbo.Suppliers s ";
            query += "         ON ";
            query += "             p.SupplierID = s.SupplierID ";
            query += " ORDER BY ";
            query += "     p.ProductID ";

            return query;
        }
    }

    public class GridModel
    {
        public int ProductId
        {
            get;
            set;
        }

        public string ProductName
        {
            get;
            set;
        }

        public string CategoryName
        {
            get;
            set;
        }

        public string CompanyName
        {
            get;
            set;
        }
    }
}

実行すると以下のようになります。

[XPDataViewのサンプル]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;

namespace XPODirectSqlSample2
{
    public partial class Form1 : Form
    {
        static readonly string SERVER = @".\SQLEXPRESS";
        static readonly string DATABASE = @"Northwind";
        
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //
            // データ接続レイヤーを設定.
            //   本来この設定はエントリーポイントで行う方がよい.
            //
            XpoDefault.DataLayer =
                XpoDefault.GetDataLayer(
                    MSSqlConnectionProvider.GetConnectionString(SERVER, DATABASE),
                    AutoCreateOption.SchemaAlreadyExists
                );

            using (var uow = new UnitOfWork())
            {
                gridControl1.DataSource = 
                    new XPDataView(
                        uow.Dictionary, 
                        uow.GetClassInfo<GridModel>(), 
                        uow.ExecuteQuery(GetQuery())
                    );
            }
        }

        private string GetQuery()
        {
            string query = string.Empty;

            query += " SELECT ";
            query += "      p.ProductID ";
            query += "     ,p.ProductName ";
            query += "     ,c.CategoryName ";
            query += "     ,s.CompanyName ";
            query += " FROM ";
            query += "     dbo.Products p ";
            query += "         INNER JOIN dbo.Categories c ";
            query += "         ON ";
            query += "             p.CategoryID = c.CategoryID ";
            query += "         INNER JOIN dbo.Suppliers s ";
            query += "         ON ";
            query += "             p.SupplierID = s.SupplierID ";
            query += " ORDER BY ";
            query += "     p.ProductID ";

            return query;
        }
    }

    [NonPersistent]
    public class GridModel : XPLiteObject
    {
        public GridModel(Session session) : base(session)
        {
        }

        [Key]
        public int ProductId
        {
            get;
            set;
        }

        public string ProductName
        {
            get;
            set;
        }

        public string CategoryName
        {
            get;
            set;
        }

        public string CompanyName
        {
            get;
            set;
        }
    }
}

実行すると以下のようになります。

[GetObjectsFromQueryのサンプル]

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

namespace XPODirectSqlSample3
{
    public partial class Form1 : Form
    {
        static readonly string SERVER   = @".\SQLEXPRESS";
        static readonly string DATABASE = @"Northwind";

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //
            // データ接続レイヤーを設定.
            //   本来この設定はエントリーポイントで行う方がよい.
            //
            XpoDefault.DataLayer =
                XpoDefault.GetDataLayer(
                    MSSqlConnectionProvider.GetConnectionString(SERVER, DATABASE),
                    AutoCreateOption.SchemaAlreadyExists
                );

            //
            // GetObjectsFromQueryメソッドを使用する。
            // このメソッドを利用するとSQLを直接発行しながら、XPOのPersistentオブジェクトの
            // リストでデータを取得することができる。
            //
            // DevExpressの以下のブログ記事
            //   http://community.devexpress.com/blogs/garyshort/archive/2010/10/08/xpo-direct-sql-queries.aspx
            // では、GetObjectsFromQuery<T>の結果をXPDataView.LoadDataに渡しているが
            // RC版のXPDataView.LoadDataには, ICollection<T>を受け付けるオーバーロードが存在しない。
            //
            using (var uow = new UnitOfWork())
            {
                //
                // DataSourceプロパティには、ICollection<GridModel>が設定される。
                //
                gridControl1.DataSource = uow.GetObjectsFromQuery<GridModel>(GetQuery());
            }
        }

        private string GetQuery()
        {
            string query = string.Empty;

            query += " SELECT ";
            query += "      p.ProductID ";
            query += "     ,p.ProductName ";
            query += "     ,c.CategoryName ";
            query += "     ,s.CompanyName ";
            query += " FROM ";
            query += "     dbo.Products p ";
            query += "         INNER JOIN dbo.Categories c ";
            query += "         ON ";
            query += "             p.CategoryID = c.CategoryID ";
            query += "         INNER JOIN dbo.Suppliers s ";
            query += "         ON ";
            query += "             p.SupplierID = s.SupplierID ";
            query += " ORDER BY ";
            query += "     p.ProductID ";

            return query;
        }
    }

    [NonPersistent]
    public class GridModel : XPLiteObject
    {
        public GridModel(Session session)
            : base(session)
        {
        }

        [Key]
        public int ProductId
        {
            get;
            set;
        }

        public string ProductName
        {
            get;
            set;
        }

        public string CategoryName
        {
            get;
            set;
        }

        public string CompanyName
        {
            get;
            set;
        }
    }
}

実行すると以下のようになります。

で、使っていて気づいたんですがExecuteXXXとGetObjectsFromQueryの両方ともに
コマンドパラメータを指定するオーバーロードが無いんですよね・・・。
サポートする予定があるかどうか、現在DevExpress側に問い合わせ中。
  →2010/11/24:サポートする予定は無いとのこと・・残念。



ついでに、LINQ To XPOについても記述。
ここから先はメモ書きみたいなものです。すみません。


結構前からXPOにはLINQ対応が入っていて、作成したPersistentオブジェクトに対して
LINQを実行することが可能となっています。


やり方は以下の手順で行います。

  1. XPQueryを構築
  2. LINQクエリを構築
  3. 実行


普通にLINQできます。Criteriaをがんばって構築するよりこっちの方が楽な場合が多いです。
ただし、残念なことにJOINに対応していません。(なんでやねん)
GroupJoinは出来るとの記述がありますが、集計系メソッドしか利用できません。(Maxとか)


それ以外の部分は対応していますので、普通にWhereとかOrderbyとかは設定できます。


JOIN機能が必須の場合は、元々の機能であるOne-to-ManyやMany-to-Many、Direct SQL Queriesを利用する方がいいです。
DevExpress製品との親和性を考慮しない場合は、Linq To SQLやEntity Frameworkを使うのもありですね。


以下、サンプルです。

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

namespace XPQuerySample
{
    public partial class Form1 : Form
    {
        static readonly string SERVER   = @".\SQLEXPRESS";
        static readonly string DATABASE = "Northwind";

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //
            // 接続データレイヤーの設定.
            //   本来はMainメソッドなどのエントリーポイントで行う方がよい。
            //
            XpoDefault.DataLayer =
                XpoDefault.GetDataLayer(
                    MSSqlConnectionProvider.GetConnectionString(SERVER, DATABASE),
                    AutoCreateOption.SchemaAlreadyExists
                );
            
            //
            // データベースからデータを取得し
            // XPQueryを用いてデータソースを構築する。
            //
            var products   = new XPQuery<Northwind.Products>(Session.DefaultSession);
            var categories = new XPQuery<Northwind.Categories>(Session.DefaultSession);
            var suppliers  = new XPQuery<Northwind.Suppliers>(Session.DefaultSession);            
            
            //
            // LINQ To XPOはjoinをサポートしていない.
            // 今回の場合は、1対1で紐づいているのでMaxメソッドで無理矢理取得。
            //
            // DevExpressのドキュメントには、GroupJoinはサポートしているとなっている。
            // 利用できるのはMaxなどの集計メソッドのみとなる。
            //
            var query = from    p in products
                        join    c in categories on p.CategoryID.CategoryID equals c.CategoryID into cg
                        join    s in suppliers  on p.SupplierID.SupplierID equals s.SupplierID into sg
                        orderby p.ProductID
                        select  new GridModel
                                {
                                    ProductId    = p.ProductID,
                                    ProductName  = p.ProductName,
                                    CategoryName = cg.Max(category => category.CategoryName),
                                    CompanyName  = sg.Max(supplier => supplier.CompanyName)
                                };

            ////
            //// 同じものをLinq To SQLで記述すると普通にJOINできる。
            ////
            //using (var db = new NorthwindLinqToSqlDataContext())
            //{
            //    var q2 = from    p in db.Products
            //             join    c in db.Categories on p.CategoryID equals c.CategoryID
            //             join    s in db.Suppliers  on p.SupplierID equals s.SupplierID
            //             orderby p.ProductID
            //             select  new GridModel
            //                     {
            //                        ProductId    = p.ProductID,
            //                        ProductName  = p.ProductName,
            //                        CategoryName = c.CategoryName,
            //                        CompanyName  = s.CompanyName
            //                     };
                
            //    gridControl1.DataSource = q2;
            //}
            
            gridControl1.DataSource = query.ToList();
        }
    }

    /// <summary>
    /// グリッドに設定されてるデータモデル
    /// </summary>
    public class GridModel
    {
        public int ProductId
        {
            get;
            set;
        }

        public string ProductName
        {
            get;
            set;
        }

        public string CategoryName
        {
            get;
            set;
        }

        public string CompanyName
        {
            get;
            set;
        }
    }
}

実行すると以下のようになります。


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