8

最近、コードがすべてこの一般的な形式をとるデータアクセス層の選択メソッドを書いていることに気づきました。

public static DataTable GetSomeData( ... arguments)
{
    string sql = " ... sql string here:  often it's just a stored procedure name ... ";

    DataTable result = new DataTable();

    // GetOpenConnection() is a private method in the class: 
    // it manages the connection string and returns an open and ready connection
    using (SqlConnection cn = GetOpenConnection())
    using (SqlCommand cmd = new SqlCommand(sql, cn))
    {
        // could be any number of parameters, each with a different type
        cmd.Parameters.Add("@Param1", SqlDbType.VarChar, 50).Value = param1; //argument passed to function

        using (SqlDataReader rdr = cmd.ExecuteReader())
        {
            result.Load(rdr);
        }
    }

    return result;
}

またはこのように:

public static DataRow GetSomeSingleRecord( ... arguments)
{
    string sql = " ... sql string here:  often it's just a stored procedure name ... ";

    DataTable dt = new DataTable();

    // GetOpenConnection() is a private method in the class: 
    // it manages the connection string and returns an open and ready connection
    using (SqlConnection cn = GetOpenConnection())
    using (SqlCommand cmd = new SqlCommand(sql, cn))
    {
        // could be any number of parameters, each with a different type
        cmd.Parameters.Add("@Param1", SqlDbType.VarChar, 50).Value = param1; //argument passed to function

        using (SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.SingleRow))
        {
            dt.Load(rdr);
        }
    }

    if (dt.Rows.Count > 0)
         return dt.Rows[0];
    return null;
}

これらのメソッドは、ベースの DataTable または DataRecord を、プレゼンテーション層が使用できる厳密に型指定されたビジネス オブジェクトに変換するビジネス層コードによって呼び出されます。

同様のコードを繰り返し使用しているので、このコードが最善であることを確認したいと思います。では、どうすれば改善できるでしょうか?そして、共通コードをこれから独自のメソッドに移動しようとする価値はありますか。もしそうなら、そのメソッドはどのように見えますか (特に SqlParameter コレクションを渡すことに関して)?

4

7 に答える 7

3

自分自身を追加する必要がありました:
Using ステートメントで DataLayer から DataReader を返す

新しいパターンを使用すると、一度に 1 つのレコードしかメモリに保持できませんが、接続は適切な 'using' ステートメントに含まれます。

public IEnumerable<T> GetSomeData(string filter, Func<IDataRecord, T> factory)
{
    string sql = "SELECT * FROM [SomeTable] WHERE SomeColumn= @Filter";

    using (SqlConnection cn = new SqlConnection(GetConnectionString()))
    using (SqlCommand cmd = new SqlCommand(sql, cn))
    {
        cmd.Parameters.Add("@Filter", SqlDbType.NVarChar, 255).Value = filter;
        cn.Open();

        using (IDataReader rdr = cmd.ExecuteReader())
        {
            while (rdr.Read())
            {
                yield return factory(rdr);
            }
            rdr.Close();
        }
    }
}
于 2009-05-12T18:15:51.957 に答える
2

私が楽しんだ1つのパターンは、クライアントコードに関する限り、次のようになります。

        DataTable data = null;
        using (StoredProcedure proc = new StoredProcedure("MyProcName","[Connection]"))
        {
            proc.AddParameter("@LoginName", loginName);
            data = proc.ExecuteDataTable();
        }

私は通常、接続をオプションにします。ConnectionStrings構成セクションから接続をプルするか、実際の接続文字列として扱うようにコーディングします。これにより、1回限りのシナリオでdalを再利用できます。これは、オブジェクト構築プロパティを使用して接続文字列を保存したCOM+時代の習慣の一部です。

読みやすく、すべてのADOコードを隠すことができるので、これが好きです。

于 2009-01-12T16:52:41.993 に答える
2

私がここに投稿したものと同様

public IEnumerable<S> Get<S>(string query, Action<IDbCommand> parameterizer, 
                             Func<IDataRecord, S> selector)
{
    using (var conn = new T()) //your connection object
    {
        using (var cmd = conn.CreateCommand())
        {
            if (parameterizer != null)
                parameterizer(cmd);
            cmd.CommandText = query;
            cmd.Connection.ConnectionString = _connectionString;
            cmd.Connection.Open();
            using (var r = cmd.ExecuteReader())
                while (r.Read())
                    yield return selector(r);
        }
    }
}

呼び出しを容易にするために、次の単純な拡張メソッドがあります。

public static void Parameterize(this IDbCommand command, string name, object value)
{
    var parameter = command.CreateParameter();
    parameter.ParameterName = name;
    parameter.Value = value;
    command.Parameters.Add(parameter);
}

public static T To<T>(this IDataRecord dr, int index, T defaultValue = default(T),
                      Func<object, T> converter = null)
{
    return dr[index].To<T>(defaultValue, converter);
}

static T To<T>(this object obj, T defaultValue, Func<object, T> converter)
{
    if (obj.IsNull())
        return defaultValue;

    return converter == null ? (T)obj : converter(obj);
}

public static bool IsNull<T>(this T obj) where T : class
{
    return (object)obj == null || obj == DBNull.Value;
}

だから今私は呼び出すことができます:

var query = Get(sql, cmd =>
{
    cmd.Parameterize("saved", 1);
    cmd.Parameterize("name", "abel");
}, r => new User(r.To<int>(0), r.To<string>(1), r.To<DateTime?>(2), r.To<bool>(3)));
foreach (var user in query)
{

}

これは完全に一般的なもので、ado.net インターフェイスに準拠するすべてのモデルに適合します。接続オブジェクトとリーダーは、コレクションが 1 回列挙された後にのみ破棄されます。

于 2013-02-14T08:12:52.317 に答える
1

私が違うのは、自分の内部データベースヘルパーメソッドから実際のデータアクセスアプリケーションブロックhttp://msdn.microsoft.com/en-us/library/cc309504.aspxに切り替えたことだけです。

エンタープライズライブラリを知っている他の開発者がコードを強化するために、もう少し標準化/統一化されています。

于 2009-01-12T16:52:19.013 に答える
1

まず、ORM の使用と独自のロールの使用を既に検討していると思います。私はこれには入りません。

独自のデータ アクセス コードを展開することについての私の考え:

  • 時間が経つにつれて、個別の DAL/BL オブジェクトを作成するよりも、それらを 1 つのオブジェクトにマージする方が簡単であることがわかりました (この結論に達してからしばらくして、これが非常によく知られたパターン、つまり ActiveRecord であることがわかりました)。別々の DAL アセンブリを使用することは見栄えがよく分離されているように見えるかもしれませんが、メンテナンス コストのオーバーヘッドが追加されます。新しい機能を追加するたびに、より多くのコードを作成したり、より多くのクラスを変更したりする必要があります。私の経験では、アプリケーションを保守するチームは、それを構築した元の開発者チームよりもはるかに少ないことが多く、余分な作業が必要になることを嫌います。
  • 大規模なチームの場合、DAL を分離するのが理にかなっている場合があります (そして、他のグループが作業している間にグループに作業を任せます)。しかし、これはコードの肥大化の良いインセンティブになります。
  • 特定のサンプルに取り掛かります:結果の DataTable をどのように使用しますか? 行を繰り返し、型付きオブジェクトを作成し、行からデータを取得しますか? 答えが「はい」の場合は、DAL と BL の間でデータを移動するためだけに作成した追加の DataTable について考えてみてください。DataReader から直接取得してみませんか?
  • サンプルについても: 型指定されていない DataTable を返す場合は、呼び出し元のコードで (SP 呼び出しが返す結果セットの) 列名を使用する必要があると思います。これは、データベースで何かを変更する必要がある場合、両方のレイヤーに影響を与える可能性があることを意味します。

私の提案(私は両方の方法を試しました - 提案は私が思いついた最新の実用的なアプローチです - それは時間の経過とともに一種の進化を遂げました)。

  • 型付きビ​​ジネス オブジェクトの基本クラスを作成します。
  • オブジェクトの状態を基本クラスに保持する (新規、変更など)
  • 主要なデータ アクセス メソッドを静的メソッドとしてこのクラスに配置します。ちょっとした努力 (ヒント: ジェネリック メソッド + Activator.CreateInstance) で、リーダーに返される各行ごとに 1 つのビジネス オブジェクトを作成できます。
  • 行データを (DataReader から直接) 解析するためのビジネス オブジェクトで抽象メソッドを作成し、オブジェクトを埋めます。
  • ストアド プロシージャ パラメーターを準備する (さまざまなフィルター基準に応じて) 派生ビジネス オブジェクトで静的メソッドを作成し、基本クラスから汎用データ アクセス メソッドを呼び出します。

目的は、次のような使用法になることです。

List<MyObject> objects = MyObject.FindMyObject(string someParam);

私にとっての利点は、データベースの列名や型などの変更 (一般的に小さな変更) に対処するために、1 つのファイルを変更するだけでよいことでした。いくつかのよく考えられた領域を使用すると、同じオブジェクト内の別々の「レイヤー」になるようにコードを整理できます:)。もう 1 つの利点は、基本クラスが 1 つのプロジェクトから別のプロジェクトに実際に再利用できることです。そして、コードの肥大化は最小限です (まあ、利点と比較して。データセットを埋めて UI コントロールにバインドすることもできます :D

制限 - ドメイン オブジェクトごとに (通常はメイン データベース テーブルごとに) 1 つのクラスになってしまいます。また、既存のトランザクションにオブジェクトをロードすることはできません (ただし、トランザクションがある場合は、それを渡すことを考えることができます)。

詳細に興味がある場合はお知らせください。回答を少し拡大できます。

于 2009-01-14T22:45:19.003 に答える
1

DBAL を実装するには非常に多くの方法がありますが、私の意見では、あなたは正しい道を進んでいます。実装で考慮すべきこと:

  • ファクトリのようなメソッドを使用して SqlConnection を作成しています。これはマイナーな点ですが、SqlCommand についても同じことができます。
  • パラメータの長さはオプションなので、実際には Parameter.Add 呼び出しから除外できます。
  • パラメーターを追加するためのメソッドも作成します。コード サンプルは以下のとおりです。

を使用してパラメータを追加しますDbUtil.AddParameter(cmd, "@Id", SqlDbType.UniqueIdentifier, Id);

internal class DbUtil {

internal static SqlParameter CreateSqlParameter(
    string parameterName,
    SqlDbType dbType,
    ParameterDirection direction,
    object value
) {
    SqlParameter parameter = new SqlParameter(parameterName, dbType);

    if (value == null) {
        value = DBNull.Value;
    }

    parameter.Value = value;

    parameter.Direction = direction;
    return parameter;
}

internal static SqlParameter AddParameter(
    SqlCommand sqlCommand,
    string parameterName,
    SqlDbType dbType
) {
    return AddParameter(sqlCommand, parameterName, dbType, null);
}

internal static SqlParameter AddParameter(
    SqlCommand sqlCommand,
    string parameterName,
    SqlDbType dbType,
    object value
) {
    return AddParameter(sqlCommand, parameterName, dbType, ParameterDirection.Input, value);
}

internal static SqlParameter AddParameter(
    SqlCommand sqlCommand,
    string parameterName,
    SqlDbType dbType,
    ParameterDirection direction,
    object value
) {
    SqlParameter parameter = CreateSqlParameter(parameterName, dbType, direction, value);
    sqlCommand.Parameters.Add(parameter);
    return parameter;
    }
}
于 2009-01-12T17:13:55.210 に答える