DataAccessパターン化(1)

.NETよりSQL Serverに接続してデータを取得する処理について
SQL Helperクラスの実装の方法がMSDNで公開されています。

http://msdn.microsoft.com/ja-jp/library/dd297738.aspx

早速ソースをコピーしてみたところ、どうもデータベースの接続を解放していないケースが
あるのではないかと思いました。

以下のソースです。


public static SqlDataReader ExecuteReader(
string connectionString,
CommandType commandType,
string commandText,
params SqlParameter[] commandParameters)
{
SqlConnection cn = new SqlConnection(connectionString);
cn.Open();
try
{
return ExecuteReader(cn, null, commandType, commandText,
commandParameters,SqlConnectionOwnership.Internal);
}
catch
{
cn.Close();
throw;
}
}

SqlConnection型のインスタンス変数の cn が、正常時に解放する処理が呼ばれてないようです。。。
というより、この関数はSqlDataReaderを返す関数なのですが、そのSqlDataReaderを呼び出し元が
使い終わるまでは、データベースへの接続(インスタンス変数 cn)は解放できないはずです。

以上を考えると、データベースへの接続と解放のタイミングは
関数の呼び出し元で制御するべきではないかと思いました。

ということで、データベースへ接続してSelect文を実行し、SqlDataReaderに結果を入れて返す
共通処理を作っていました。
今後は自分が主に色々なところで使っていくと思うので、備忘録として載せておきます。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Xml;

namespace WhiskeyTastingApplication.DAL
{
public class DataAccess : IDisposable
{
private SqlConnection con = null;

public DataAccess(string connectionString)
{
con = new SqlConnection(connectionString);
con.Open();
}

public SqlDataReader ExecuteReader(CommandType commandType,
string commandText)
{
using (SqlCommand cmd = new SqlCommand(commandText, con))
{
cmd.CommandType = commandType;
return cmd.ExecuteReader();
}
}

~DataAccess()
{
Dispose(false);
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Clean up all managed resources
}

// Clean up all native resources
if (con != null)
{
con.Close();
con.Dispose();
}
}

}
}

特徴としては
・DataAccessクラスのコンストラクタ内でデータベースとの接続をOpen()すること
・IDisposeパターンを実装し、Dispose もしくは ファイナライザで接続が解放されること
です。

呼び出し元からみると
・データベースと接続するタイミングはコンストラクタの呼び出し時
・接続を解放するタイミングはDispose時(もしくはファイナライズ時)
となります。

またDataAccessクラスをnew した時に接続がOpenされるので
呼び出し側ではSQL文を実行する直前までnew しないほうがいいでしょう。

このDataAccessクラスを使用例も載せておきます。


public class PartyDAL
{
private readonly string connectionString = "Server=サーバー名;
Database=データベース名;
Trusted_Connection=Yes";
private enum party { party_id, name, startDateTime, endDateTime, place };

public IList GetParty()
{
StringBuilder sql = new StringBuilder();
sql.Append("select ");
sql.Append(" party_id, ");
sql.Append(" info.query('/party/name'), ");
sql.Append(" info.query('/party/startDateTime'), ");
sql.Append(" info.query('/party/endDateTime'), ");
sql.Append(" info.query('/party/place') ");
sql.Append("from ");
sql.Append(" party ");

IList parties = null;

using (DataAccess dataAccess = new DataAccess(connectionString))
{
using (SqlDataReader reader = dataAccess.ExecuteReader(CommandType.Text, sql.ToString()))
{
while (reader.Read())
{
if (parties == null) parties = new List();
parties.Add(new PartyModel(Int32.Parse(reader.GetValue*1,
SqlXmlReader.GetValue(reader, (int)party.name),
SqlXmlReader.GetDateTimeValue(reader, (int)party.startDateTime),
SqlXmlReader.GetDateTimeValue(reader, (int)party.endDateTime),
SqlXmlReader.GetValue(reader, (int)party.place)));
}
reader.Close();
}
}

return parties;
}
}

実行するSQL文をあらかじめ定義しておき、SQL実行直前にDataAccessクラスをnew()して、
ExecuteReader()を呼び出して結果をSqlDataReader型の変数に取得します。
SqlDataReader、DataAccessクラスはusing句で使用が終わったら解放します。

*1:int)party.party_id).ToString(