いろいろ備忘録日記

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

.NET クラスライブラリ探訪-039 (System.IO.MemoryMappedFiles.MemoryMappedFile)(メモリマップトファイル, ランダムアクセス, 共有メモリ, 4.0)


System.IO.MemoryMappedFiles.MemoryMappedFileクラスは、.NET 4.0から
追加されたクラスです。文字通り、メモリ上にマッピングされたファイルを扱います。
MMFと略したりします。


メモリマップトファイルは、昔からWin32 APIとして用意されていましたが
4.0より.NET Frameworkにクラスとして登場しました。


MemoryMappedFileの特徴は、ランダムアクセスが速い事です。
メモリ内にマップされているのでシークする必要がありません。
逆にシーケンシャルアクセスは苦手です。


逆にFileStreamはシーケンシャルアクセスが得意で、ランダムアクセスが苦手です。


また、MemoryMappedFileは共有メモリとしても利用出来ます。
つまり複数のプロセス間で共有することが出来ます。


で、実際の利用方法ですが
MemoryMappedFileオブジェクトは、newするのではなくMemoryMappedFileクラス
に用意されている以下のstaticメソッドを利用して構築します。

  • CreateFromFile
    • ディスク上のファイルからMemoryMappedFileを作成します。
  • CreateNew
    • ディスク上のファイルと関連づけられていないMemoryMappedFileを作成します。
  • CreateOrOpen
    • ディスク上のファイルと関連づけられていないMemoryMappedFileを作成もしくは開きます。
  • OpenExisting
    • 指定された名前の既にマッピングされているMemoryMappedFileを開きます。


上記のどのメソッドを利用する場合でも、通常MemoryMappedFileオブジェクトを
構築する際は、以下のように2つのusingを利用します。

using (var mmf = MemoryMappedFile.CreateFromFile(BIN_FILE_NAME))
{
    using (var accessor = mmf.CreateViewAccessor())
    {
        // データの読み書きを行う。
    }
}

一つ目のusingでMemoryMappedFileオブジェクトを取得し、二つ目のusingで
データにアクセスするためのMemoryMappedViewAccessorを取得します。


後は取得したMemoryMappedViewAccessorでデータを読み書きします。


データの読み書きには以下のメソッドを利用します。

  • ReadXXX (XXXの部分にはそれぞれの型名が入る。(int32, byte, double, charなど))
    • 指定された型でデータを取得します。
  • Read
    • T型の構造体を指定してデータを読み取ります。
  • ReadArray
    • T型の配列を指定してデータを読み取ります。
  • Write
    • 指定したデータを書き込み。(各型用にオーバーロードが用意されています。)
  • Write
    • T型の構造体を指定してデータを書き込みます。
  • WriteArray
    • T型の配列を指定してデータを書き込みます。


以下、データの基本的な読み書きのサンプルです。
以下のサンプルでは最初に大きなバイナリファイルを作成して
そのファイルをメモリ上にマッピングし、データを読み書きしています。

using System;
using System.IO;
using System.IO.MemoryMappedFiles;

namespace MemoryMappedFilesSample
{
    class Program
    {
        private const string BIN_FILE_NAME = "bigdata.bin";

        static void Main(string[] args)
        {
            if (File.Exists(BIN_FILE_NAME))
            {
                File.Delete(BIN_FILE_NAME);
            }

            //
            // 元となるバイナリファイルを作成.
            //
            File.WriteAllBytes(BIN_FILE_NAME, new byte[1000000]);
            
            //
            // 元々ファイルが存在する状態での読み込み.
            //
            using (var mmf = MemoryMappedFile.CreateFromFile(BIN_FILE_NAME))
            {
                using (var accessor = mmf.CreateViewAccessor())
                {
                    //
                    // 書き換える前のデータを確認.
                    //
                    Console.WriteLine(accessor.ReadByte(500000));

                    //
                    // データを書き換える.
                    //
                    accessor.Write(500000, (byte) 77);
                    
                    //
                    // 書き換えた位置のデータを取得して確認.
                    //
                    Console.WriteLine(accessor.ReadByte(500000));

                    //
                    // 配列を指定して指定範囲のデータを取得.
                    //
                    byte[] buf = new byte[10];
                    accessor.ReadArray<byte>(499996, buf, 0, buf.Length);

                    Console.WriteLine(string.Join<byte>(",", buf));

                    //
                    // 配列を指定して指定範囲のデータを設定.
                    //
                    buf = new byte[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
                    accessor.WriteArray<byte>(499996, buf, 0, buf.Length);

                    buf = new byte[10];
                    accessor.ReadArray<byte>(499996, buf, 0, buf.Length);
                    Console.WriteLine(string.Join<byte>(",", buf));
                }
            }
        }
    }
}

実行結果は以下のようになります。

0
77
0,0,0,0,77,0,0,0,0,0
10,20,30,40,50,60,70,80,90,100


次に、MemoryMappedFileを共有メモリとして利用しているサンプルです。
以下のサンプルは2つのexeを用意して、一つはデータの書き込み、一つはデータの読み込みを
同じ共有メモリに対して行います。その際、MemoryMappedFileには同じ名前を指定するようにします。


まず書き込み側。

using System;
using System.IO.MemoryMappedFiles;
using System.Threading;

namespace CreateNewMemoryMappedFile
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var mutex = new Mutex(true, "MMF_MUTEX"))
            {
                Console.WriteLine("===LOCK=== {0} {1}", DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond);

                //
                // 新規でMemory Mapped Fileを作成.
                //
                using (var mmf = MemoryMappedFile.CreateNew("data.bin", 500))
                {
                    using (var accessor = mmf.CreateViewAccessor())
                    {
                        accessor.Write(10, 12345);

                        mutex.ReleaseMutex();
                        Console.WriteLine("12345 -- write");
                        Console.WriteLine("===RELEASE=== {0} {1}", DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond);

                        Console.WriteLine("CreateNewMemoryMappedFile -- Write");
                        Console.ReadLine();
                    }
                }
            }
        }
    }
}


次に読み込み側

using System;
using System.IO.MemoryMappedFiles;
using System.Threading;

namespace ReadSharedMemoryMappedFile
{
    class Program
    {
        static void Main(string[] args)
        {
            Mutex mutex = null;
            while (mutex == null)
            {
                try
                {
                    mutex = Mutex.OpenExisting("MMF_MUTEX");
                }
                catch
                {
                    // re-try.
                    Console.WriteLine("Retry....");
                }                
            }

            //
            // すでに存在するMemory Mapped Fileを読み込み.
            //
            using (mutex)
            {
                if (mutex.WaitOne())
                {
                    Console.WriteLine("===LOCK=== {0} {1}", DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond);

                    int data = -1;
                    using (var mmf = MemoryMappedFile.OpenExisting("data.bin"))
                    {
                        using (var accessor = mmf.CreateViewAccessor())
                        {
                            data = accessor.ReadInt32(10);
                        }
                    }

                    mutex.ReleaseMutex();
                    Console.WriteLine("{0} -- Read", data);
                    Console.WriteLine("===RELEASE=== {0} {1}", DateTime.Now.ToLongTimeString(), DateTime.Now.Millisecond);

                    Console.WriteLine("ReadSharedMemoryMappedFile -- Read");
                    Console.ReadLine();
                }
            }
        }
    }
}


最後に二つを起動するプログラム

using System;
using System.Diagnostics;

namespace MemoryMappedFilesSample2
{
    class Program
    {
        static void Main(string[] args)
        {
            //
            // 2つのExeを順に起動.
            //
            Process.Start("CreateNewMemoryMappedFile.exe");
            Process.Start("ReadSharedMemoryMappedFile.exe");

            Console.WriteLine("MemoryMappedFileSample2 -- Main");
            Console.ReadLine();
        }
    }
}

実行すると、書き込み側では

12345 -- Write

と表示され、読み込み側では

12345 -- Read

と表示されます。


また、説明の部分でも記述しましたがaccessorにデータを設定する際に
構造体を指定することも出来ます。(Read, Write)
こうするとデータを纏めて読み書きできます。


構造体を利用する際の注意点ですが、この場合の構造体には
参照を含む型は定義できません。つまり構造体のメンバーとして

public char[] Chars;

とは定義できません。定義できるのは参照を含まない型のみです。(intなど)


んじゃ、文字列はどうするの?となるのですが
文字列の場合は、バイト配列に変換してWriteArrayで書き込みます。
当然読み込みはReadArrayを利用します。


以下、構造体と文字列を使用したサンプルです。

using System;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Text;

namespace MemoryMappedFilesSample3
{
    class Program
    {
        static void Main(string[] args)
        {
            //
            // 500バイトのMMFを新規作成する.
            //
            using (var mmf = MemoryMappedFile.CreateNew("data.bin", 500))
            {
            	using (var accessor = mmf.CreateViewAccessor())
                {
                    //
                    // 構造体と文字列を書き込み.
                    //    構造体を指定する場合、その構造体に参照を含む型が存在していると
                    //    エラーとなる。
                    //
                    // 文字列を書き込む場合は、Encodingクラスを利用してバイト配列に変換し
                    // WriteArray<T>メソッドを利用して書き込む。
                    //
                    // 読み込む場合は、ReadArray<T>を利用して読み込む.
                    //
                    // 構造体のサイズを取得する場合は、Marshal.SizeOfメソッドを利用する.
                    //
                    Data data;
                    data.X = 100;
                    data.Y = 200;

                    byte[] bytes = Encoding.UTF8.GetBytes("Hello World");

                    //
                    // データ書き込み.
                    //
                    const int START_POSITION = 10;
                    int structSize = Marshal.SizeOf(data);

                    accessor.Write<Data>(START_POSITION, ref data);
                    accessor.WriteArray<byte>(START_POSITION + structSize, bytes, 0, bytes.Length);                    

                    //
                    // データ読み込み.
                    //
                    Data data2;
                    accessor.Read<Data>(START_POSITION, out data2);

                    byte[] bytes2 = new byte[bytes.Length];
                    structSize = Marshal.SizeOf(data2);
                    accessor.ReadArray<byte>(START_POSITION + structSize, bytes2, 0, bytes2.Length);

                    //
                    // 確認.
                    //
                    Console.WriteLine(data2);
                    Console.WriteLine(Encoding.UTF8.GetString(bytes2));
                }
            }
        }
    }

    struct Data
    {
        public int X;
        public int Y;

        public override string ToString()
        {
            return string.Format("X={0}, Y={1}", X, Y);
        }
    }
}

実行すると

X=100, Y=200
Hello World

と表示されます。



最後に、unsafeブロックを使用して直接ポインタ経由で
データを読み書きするサンプルです。


ポインタを取得するには、まずメモリマップトファイルのビューハンドルを
取得し、そこからメモリブロックのポインタを取得します。
ビューハンドルはMemoryMappedViewAccessorの以下のメソッドで取得します。

accessor.SafeMemoryMappedViewHandle

ビューハンドルを取得したらそのままAcquirePointerメソッドでポインタを取ります。
後はそのポインタを利用して読み書きします。


以下、サンプルです。

using System;
using System.IO.MemoryMappedFiles;

namespace MemoryMappedFilesSample4
{
    class Program
    {
        static void Main(string[] args)
        {
            unsafe
            {
                using (var mmf = MemoryMappedFile.CreateOrOpen("data.bin", 500))
                {
                    //
                    // ポインタを利用してデータ書き込み.
                    //
                	using (var accessor = mmf.CreateViewAccessor())
                    {
                        //
                        // メモリマップトファイルのビューハンドルを取得し
                        // メモリブロックのポインタを取得.
                        //
                    	byte* p = null;
                        accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref p);

                        //
                        // 取得したポインタを任意の型のポインタにキャストしてデータを設定.
                        //
                        Data* data = (Data*) p;

                        data->X = 100;
                        data->Y = 200;
                    }

                    //
                    // ポインタを取得してデータを読み込み.
                    //
                    using (var accessor = mmf.CreateViewAccessor())
                    {
                        //
                        // メモリマップトファイルのビューハンドルを取得し
                        // メモリブロックのポインタを取得.
                        //
                        byte* p = null;
                        accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref p);

                        //
                        // 書き込み時に利用した型のポインタにキャストしてデータを読み込み.
                        //
                        Data* data = (Data*) p;

                        Console.WriteLine(*data);
                    }                
                }
            }
        }
    }

    struct Data
    {
        public int X;
        public int Y;

        public override string ToString()
        {
            return string.Format("X={0}, Y={1}", X, Y);
        }
    }
}

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

サンプルコードは、以下の場所で公開しています。

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


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

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


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

Progress reporting in C# 5 async (CodeProject, 5.0, async, await, TAP, Task-based Asynchrony Pattern)


CodeProjectで見かけたので、とりあえずメモメモ。
まだちゃんと読んでいないですが、C# 5.0のasyncとawaitを利用しての
タスクの進捗報告のやり方が記述されている模様。


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

いろいろ備忘録日記まとめ
http://sites.google.com/site/gsfzero1/

DevExpress奮闘記-066 (DXRichEditとDXSchedulerのサンプル, v2010 vol.2, チュートリアル, サンプル)


v2010 vol.2絡みですが、DevExpressのサンプルエリアにvol.2から
新登場のDXRichEditとDXSchedulerのチュートリアルサンプルがありましたのでメモメモ。


それぞれのサンプルは複数回にまたがっているので以下にリンクを張っておきます。


[DXRichEdit](E2586, E2587, E2588, E2593)


[DXScheduler](E2493, E2495, E2499, E2496, E2497)

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

いろいろ備忘録日記まとめ
http://sites.google.com/site/gsfzero1/

DevExpress奮闘記-065 (v2010 vol.2のRCリリース, インストーラが日本語化)


ついに、RCがリリースされました。
今回は、新機能が多いですね。What's Newが50ページ以上あります。
バージョン的には、2.2になるみたいですね。


今回からインストーラーが日本語化されています!



内容に関しては、まだ見ている最中なのでとりあえずリンクをメモメモ。


WPFSilverlight周りのBreaking Changesが多いですね。後XAFも。
Xpfに向けての変更と思われます。

WinFormは、XtraReport, XtraCharts周りのBreking Changesが多いです。
ASP.NETは、目立ったBreking Changesは無しですね。


以下は、目立たないですが結構間違えそうなのでメモ。

All WPF Products 
・BC1017: The "Azure" theme has been renamed to "DeepBlue".


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

SQL Azure での開発および展開, SQL Azure のセキュリティ ガイドラインが公開 (MSDN, SQL Azure, クラウド, SQL Server 2008 R2)


SQL Azure での開発および展開とSQL Azure のセキュリティ ガイドラインが
公開されています。メモメモ・・・。


XPS形式とPDF形式でダウンロードできます。


尚、SQL Azureのトップページからも、その他のホワイトペーパーが
ダウンロードできます。


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

Linq入門記-53 (補足, .NET 4.0で追加された遅延評価されるメソッド, System.IO.File.ReadLines)

今回も、ちょっとした補足事項です。


.NET 4.0になり、今まで存在していたメソッドの遅延評価版が追加されたクラスが
いくつかあります。そのうちの一つが

System.IO.File

クラスで、以下のメソッドが追加されています。

  • ReadLines


上記のメソッドは、従来から存在していた以下のメソッドの遅延評価版です。

  • ReadAllLines


書式は以下の通り。

public static IEnumerable<string> ReadLines(
	string path
)

public static IEnumerable<string> ReadLines(
	string path,
	Encoding encoding
)


前回のDirectory.EnumerateFilesと同様、戻り値がIEnumerableになっています。
遅延評価されるということは、LINQで処理しやすいということです。


今までだと、ReadAllLinesメソッドは内部で合致する情報のコレクションを
構築してから値を返してくれていました。つまり、内容を列挙する場合
完全にコレクションが完成するまで待つ必要がありました。


ReadLinesメソッドの場合は、コレクションが完成する前に列挙を始める事が
可能です。LINQを利用すると、whereで必要な情報のみに絞り込んだりするのも楽です。


以下、サンプルです。
このサンプルでは、最初に巨大なファイルを作成した後
それぞれのメソッドで列挙処理が開始されるまでの時間を計測して表示しています。

    #region LinqSamples-50
    public class LinqSamples50 : IExecutable
    {
        public void Execute()
        {
            //
            // File.ReadLinesメソッドは、従来までの
            // File.ReadAllLinesメソッドと同じ動作するメソッドである。
            //
            // 違いは、戻り値がIEnumerable<string>となっており
            // 遅延評価される。
            //
            // ReadAllLinesメソッドの場合は、全リストを構築してから
            // 戻り値が返却されるので、コレクションが構築されるまで
            // 待機する必要があるが、ReadLinesメソッドの場合は
            // コレクション全体が返される前に、列挙可能である。
            //
            Console.WriteLine("ファイル作成中・・・・");
            
            var tmpFilePath = CreateSampleFile(1000000);
            if (string.IsNullOrEmpty(tmpFilePath))
            {
                Console.WriteLine("ファイル作成中にエラー発生");
            }
            
            Console.WriteLine("ファイル作成完了");
            
            try
            {
                var watch   = Stopwatch.StartNew();
                var elapsed = string.Empty;
                
                var numberFormatInfo = new NumberFormatInfo { CurrencySymbol = "gsf_zero" };
                
                //
                // File.ReadAllLines
                //
                var query = from   line in File.ReadAllLines(tmpFilePath)
                            where  int.Parse(line, NumberStyles.AllowCurrencySymbol, numberFormatInfo) % 2 == 0
                            select line;
                
                foreach (var element in query)
                {
                    if (watch != null)
                    {
                        watch.Stop();
                        elapsed = watch.Elapsed.ToString();
                        watch = null;
                    }
                    
                    //Console.WriteLine(element);
                }
                
                Console.WriteLine("================== ReadAllLines          : {0} ==================", elapsed);
                
                //
                // File.ReadLines
                //
                watch   = Stopwatch.StartNew();
                elapsed = string.Empty;
                
                query = from   line in File.ReadLines(tmpFilePath)
                        where  int.Parse(line, NumberStyles.AllowCurrencySymbol, numberFormatInfo) % 2 == 0
                        select line;
                
                foreach (var element in query)
                {
                    if (watch != null)
                    {
                        watch.Stop();
                        elapsed = watch.Elapsed.ToString();
                        watch = null;
                    }
                    
                    //Console.WriteLine(element);
                }
                
                Console.WriteLine("================== ReadLines             : {0} ==================", elapsed);
            }
            finally
            {
                if (File.Exists(tmpFilePath))
                {
                    File.Delete(tmpFilePath);
                }
            }
        }
        
        string CreateSampleFile(int lineCount)
        {
            var tmpFileName = Path.GetTempFileName();
            
            try
            {
                //
                // 巨大なファイルを作成する.
                //
                using (var writer = new StreamWriter(new BufferedStream(File.OpenWrite(tmpFileName))))
                {
                    for (int i = 0; i < lineCount; i++)
                    {
                        writer.WriteLine(string.Format("gsf_zero{0}", i));
                    }
                    
                    writer.Flush();
                    writer.Close();
                }
            }
            catch
            {
                if (File.Exists(tmpFileName))
                {
                    File.Delete(tmpFileName);
                }
                
                return string.Empty;
            }

            return tmpFileName;
        }
    }
    #endregion


実行結果は以下の通りです。

  ファイル作成中・・・・
  ファイル作成完了
  ================== ReadAllLines          : 00:00:00.4391589 ==================
  ================== ReadLines             : 00:00:00.0008055 ==================


また、Fileクラスにはこの他にも

  • AppendAllLines
  • WriteAllLines

メソッドが追加されています。どちらもIEnumerable(Of String)を
受け取るようになっています。



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

DevExpress奮闘記-064 (v2010 vol.2の新機能まとめ, リリース前)


DevExpress本家ブログのv2010 vol.2の新機能記事もだいぶ多くなってきたので
一度まとめてみようと思います。尚、基本Webinar系の記事は省いています。


[XPO]


[Windows Forms]


[WPF, Silverlight]


[ASP.NET]


[ASP.NET MVC]


[XAF]


やっぱりWPFASP.NET系の記事が多いですね。
個人的に今回一番期待しているのはXPOです。



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

いろいろ備忘録日記まとめ
http://sites.google.com/site/gsfzero1/

ASP.NET MVC Music Store チュートリアル (ASP.NET MVC, サンプル, MSDN)


ASP.NET MVCのチュートリアルが公開されていたので、とりあえずメモメモ・・・。
PetStoreみたいな感じっぽい。


XPS形式とPDF形式でドキュメントをダウンロードすることもできます。

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

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

いろいろ備忘録日記まとめ
http://sites.google.com/site/gsfzero1/

ASP.NET MVC 3 RCがリリース (ASP.NET MVC, 下位互換性あり, Razor, NuGet Package Manager)

ついでに、メモメモ。
バージョン3のRCがリリースされています。おなじみScottGuさんのブログより。


以前のバージョンとの互換性は保たれているようです。



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

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

いろいろ備忘録日記まとめ
http://sites.google.com/site/gsfzero1/