「
RDBMS利用時のアーキテクチャの選択方針」では、バッチ処理では生ADO.netが相性が良いと書きました。ただこのクラス群はさすがに古くて、あまり積極的に使いたくないなー。と思ってしまうところがあります。
特に、SQLを実行して結果を取得しない「DbCommand.ExecuteNonQuery」や単一の値を求める「DbCommand.ExecuteScalar」であればともかく、SELECTして複数行にわたって処理する場合、「DbCommand.ExecuteReader」を使う必要がありますが、どう頑張ったってこんなコードになっちゃう。
using (var conn = new SqlConnection(connectionString))
using (var cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = selectCmd;
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
xxx = reader[columnName];
// いろいろ処理
}
}
}
んむー。せめてwhileじゃなくてforeachを使いたい。できればLINQに繋げたい。そのためには、SELECTの結果のレコードを、IEnumerable<T>で取得できるようなメソッドを用意しておきたい。そこで、DbCommandクラスにこんな拡張メソッドを用意してみます。なお、わかりやすくするためにパラメータのNULLチェックはなしで。
public static IEnumerable<IDataRecord> ExecuteQuery(this DbCommand command)
{
return ExecuteQuery(command, dr => dr);
}
public static IEnumerable<T> ExecuteQuery<T>(
this DbCommand command, Func<IDataRecord, T> mapper)
{
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
yield return mapper(reader);
}
}
}
「ExecuteQuery」という名前の2つの拡張メソッドを作ります。一つはIEnumerable<IDataRecord>を返し、もう一方は任意の変換関数を用意して、IEnumerbale<T>を返します。ちなみに「IDataRecord」はDbCommand.ExecuteReaderメソッドが返す、DbDataReader抽象クラスの派生クラスで実装されます。
これを使うと多少ましになります。例としてなんか適当なバッチ処理を考えてみましょうか。
SQLServerのあるテーブルをCSVファイルにエクスポートします。その際に「exported」カラムが「0」のレコードのみを対象とします。また、エクスポート実行後、対象レコードの「exported」カラムを「1」に、「exportAt」カラムに現在日時をセットします。
こんなありがちな処理を先のDbCommand拡張メソッドを使って書いてみると、こんなコードになります。
const string connectionString =
@"Data Source=.\SQLEXPRESS;Initial Catalog=Test;Integrated Security=True";
const string selectCmd =
"select id, uid, insertAt, qty from TestTable_1 where exported=0";
const string updateCmd =
"update TestTable_1 set exported=1, exportAt=getdate() where id in ({0})";
static void Main(string[] args)
{
using (var conn = new SqlConnection(connectionString))
{
var ids = new List<int>();
using (var cmd = conn.CreateCommand())
using (var csv = new StreamWriter("output.csv"))
{
conn.Open();
cmd.CommandText = selectCmd;
foreach (var r in cmd.ExecuteQuery())
{
ids.Add(r.GetInt32(0));
csv.WriteLine(string.Format("{0},{1},{2}", r[1], r[2], r[3]));
}
}
if (ids.Count > 0)
{
using (var cmd = conn.CreateCommand())
{
cmd.CommandText = string.Format(updateCmd, string.Join(",", ids));
cmd.ExecuteNonQuery();
}
}
}
}
意外と見やすいコードが書けるんじゃないかと思ってるんですが、どうでしょ?