ここでは注意してください。IDisposableを実装するローカルオブジェクトには、常にusingステートメントが必要です。これには、接続とリーダーだけでなく、コマンドも含まれます。しかし、それは時々、そのusingステートメントがどこに行くのか正確にトリッキーになる可能性があります。注意しないと問題が発生する可能性があります。たとえば、次のコードでは、usingステートメントを使用する前にリーダーを閉じます。
DataReader MyQuery()
{
string sql="some query";
using (var cn = new SqlConnection("connection string"))
using (var cmd = new SqlCommand(sql, cn))
{
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
return rdr;
}
}
}
代わりに、4つのオプションがあります。1つは、関数を呼び出すまでusingブロックの作成を待つことです。
DataReader MyQuery()
{
string sql="some query";
using (var cn = new SqlConnection("connection string"))
using (var cmd = new SqlCommand(sql, cn))
{
cn.Open();
return cmd.ExecuteReader();
}
}
using (var rdr = MyQuery())
{
while (rdr.Read())
{
//...
}
}
もちろん、そこでの接続には注意が必要です。つまり、関数を使用するすべての場所でusingブロックを作成することを忘れないでください。
オプション2は、メソッド自体でクエリ結果を処理するだけですが、これにより、プログラムの他の部分からのデータレイヤーの分離が解除されます。3番目のオプションは、MyQuery()関数がwhile(rdr.Read())ループ内で呼び出すことができるAction型の引数を受け入れることですが、それは厄介です。
私は一般的にオプション4を好みます:データリーダーを次のようにIEnumerableに変えます:
IEnumerable<IDataRecord> MyQuery()
{
string sql="some query";
using (var cn = new SqlConnection("connection string"))
using (var cmd = new SqlCommand(sql, cn))
{
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return rdr;
}
}
}
これで、すべてが正しく閉じられ、それを処理するコードがすべて1か所にまとめられます。また、すばらしいボーナスが得られます。クエリ結果は、どのlinq演算子でもうまく機能します。
最後に、次に私が遊んでいる新しいものは、IEnumerableとデリゲート引数の受け渡しを組み合わせた完全に新しいプロジェクトを構築することになります。
//part of the data layer
private static IEnumerable<IDataRecord> Retrieve(string sql, Action<SqlParameterCollection> addParameters)
{
//DL.ConnectionString is a private static property in the data layer
// depending on the project needs, it can be implementing to read from a config file or elsewhere
using (var cn = new SqlConnection(DL.ConnectionString))
using (var cmd = new SqlCommand(sql, cn))
{
addParameters(cmd.Parameters);
cn.Open();
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
yield return rdr;
}
}
}
そして、次のようにデータレイヤー内で使用します。
public IEnumerable<IDataRecord> GetFooChildrenByParentID(int ParentID)
{
//I could easily use a stored procedure name instead, and provide overloads for commandtypes.
return Retrieve(
"SELECT c.*
FROM [ParentTable] p
INNER JOIN [ChildTable] c ON c.ParentID = f.ID
WHERE f.ID= @ParentID", p =>
{
p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
}
);
}