SQL Server Compact コネクション確立パフォーマンス

クライアントアプリケーションでSQL Server Compact(4.0)を利用しています。
コネクションの確立が結構重い処理だという雰囲気があったので、調べてみました。

using System;
using System.Data.SqlServerCe;
using System.Diagnostics;
using System.IO;

namespace SqlCeConnectionPerformance
{
    /// <summary>
    /// SqlCeConnectionのConnection.Openがコストが高い処理であることを検証するプログラム
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            // 準備(ファイル作成、DDL、データ登録)
            const int TEST_COUNT = 1000;
            const string SDF_PATH = @".\Test.sdf";

            File.Delete(SDF_PATH);

            var connectionString = string.Format(@"Data Source={0}", SDF_PATH);

            using (var engine = new SqlCeEngine(connectionString))
                engine.CreateDatabase();

            var ddlConnection = new SqlCeConnection(connectionString);
            var ddlCommand = ddlConnection.CreateCommand();
            ddlCommand.CommandText = @"
CREATE TABLE Hoge(
    Id int IDENTITY (0,1) PRIMARY KEY,
    Name nvarchar(20) NOT NULL
)";
            using (ddlConnection)
            {
                ddlConnection.Open();
                ddlCommand.ExecuteNonQuery();
            }

            var insertConnection = new SqlCeConnection(connectionString);
            var insertCommand = insertConnection.CreateCommand();

            using (insertConnection)
            {
                insertConnection.Open();

                for (int i = 0; i < TEST_COUNT; i++)
                {
                    insertCommand.CommandText =
                        string.Format(@"INSERT INTO Hoge(Name) VALUES('{0}')", i);
                    insertCommand.ExecuteNonQuery();
                }
            }

            // 検証 -> Openは一回
            var select0Connection = new SqlCeConnection(connectionString);
            var select0Command = select0Connection.CreateCommand();

            var watch0 = Stopwatch.StartNew();
            using (select0Connection)
            {
                select0Connection.Open();

                for (int i = 0; i < TEST_COUNT; i++)
                {
                    select0Command.CommandText =
                        string.Format(@"SELECT * FROM Hoge WHERE Id = {0}", i);
                    var obj = select0Command.ExecuteScalar();
                }
            }

            Console.WriteLine(watch0.ElapsedMilliseconds); // 3回実行平均値 : 275ms

            // 検証 -> 毎回Open
            var watch1 = Stopwatch.StartNew();
            for (int i = 0; i < TEST_COUNT; i++)
            {
                var select1Connection = new SqlCeConnection(connectionString);
                var select1Command = select1Connection.CreateCommand();

                using (select1Connection)
                {
                    select1Connection.Open();
                    select1Command.CommandText =
                        string.Format(@"SELECT * FROM Hoge WHERE Id = {0}", i);
                    var obj = select1Command.ExecuteScalar();
                }
            }

            Console.WriteLine(watch1.ElapsedMilliseconds); // 3回実行平均値 : 16845ms
        }
    }
}

歴然とした差があります。UIでのインタラクション処理(データ作成->保存など)で毎回Open処理が行われるのはまずいですね。
コネクションを開きっぱなしにする方向で考えてみます。この辺り、サーバーサイドのノウハウとは異なるところだと思います。

ADO.NETにおける3種類のタイムアウト

ようやっと自分の中で整理できたので(遅!)、ここでまとめてみます。

接続タイムアウト

IDbConnection.Open() の制限時間を設定するものです。接続文字列(秒)で指定します。

Data Source=.\sqlexpress;Integrated Security=SSPI;Connection Timeout=60

コマンドタイムアウト

文字通り、1つのIDbCommandコマンドの制限時間です。IDbCommand.CommandTimeoutプロパティ(秒)で設定します。
私の場合、下記トランザクションタイムアウトの値を秒に変換してそのまま設定するようにしています。

トランザクションタイムアウト

1つのトランザクションの制限時間です。
トランザクションの実装にSystem.Transaction.TransactionScopeを利用することが多いかと思いますが、
その場合に意識する必要があります。

トランザクションタイムアウトは2箇所で設定可能です。

アプリケーション構成ファイル

未設定の場合、既定値は1分です。

<configuration>
 <system.transactions>
  <!-- トランザクションタイムアウトを30秒に短縮  -->
  <defaultSettings timeout="00:00:30" />
 </system.transactions>
</configuration>
TransactionOptions.Timeout (TransactionScopeインスタンス作成時に指定可能)

上記構成と同時に指定した場合、こちらの値が優先されます。


ただし、上記2つの方法ではシステム既定値である10分を超えた値を設定することはできません。
10分を超えた値を指定したい場合は、管理者としてログインしてマシン構成ファイル(machine.config)
以下の要領で設定を行う必要があります。

<configuration>
 <system.transactions>
  <!-- トランザクションタイムアウトを1時間に延長  -->
  <machineSettings maxTimeout="01:00:00" />
 </system.transactions>
</configuration>

結構面倒な仕様ですね...
なお、構成で設定された値はそれぞれTransactionManager.DefaultTimeoutおよびTransactionManager.MaximumTimeoutで取得可能です。