如何改进数据访问层选择方法模式

时间:2009-01-12 16:39:27

标签: .net .net-2.0 data-access-layer

最近我发现自己正在编写数据访问层选择方法,其中代码都采用这种通用形式:

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集合)?

7 个答案:

答案 0 :(得分:3)

不得不加我自己的:
Return DataReader from DataLayer in Using statement

新模式使我一次只能在内存中有一条记录,但仍然在一个很好的'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();
        }
    }
}

答案 1 :(得分:2)

就客户端代码而言,我喜欢的一种模式就是这样:

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

我通常将连接设置为可选,并且我将以从ConnectionStrings配置部分拉出它的方式进行编码或将其视为实际的连接字符串。这让我可以在一个场景中重复使用dal,并且在使用对象构造属性存储连接字符串的COM +天中部分是habbit。

我喜欢这个,因为它很容易阅读并隐藏了我的所有ADO代码。

答案 2 :(得分:1)

我唯一不同的做法是从我自己的内部数据库助手方法切换到实际的数据访问应用程序块http://msdn.microsoft.com/en-us/library/cc309504.aspx

让知道企业库的其他开发人员更加标准化/统一,以提升代码。

答案 3 :(得分: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;
    }
}

答案 4 :(得分:1)

首先,我认为你已经考虑过使用ORM而不是自己动手。我不会进入这个。

我对滚动您自己的数据访问代码的想法:

  • 随着时间的推移,我发现更容易没有单独的DAL / BL对象,而是将它们合并为一个对象(在得出这个结论后的一段时间后,我发现它是一个非常着名的模式 - 即ActiveRecord)。它可能看起来很好并且分离以具有单独的DAL组件,但是维护成本的开销将增加。每次添加新功能时,都必须创建更多代码/修改更多类。根据我的经验,维护应用程序的团队通常比构建它的开发人员团队要少,他们会讨厌所需的额外工作。
  • 对于大型团队来说,分离DAL可能是有意义的(让一个小组与其他小组合作。但这对于代码膨胀是一个很好的激励。
  • 归结为您的具体示例:您如何使用生成的DataTable?迭代行,创建类型化对象并从行中获取数据?如果答案是肯定的,请考虑您为在DAL和BL之间移动数据而创建的额外DataTable。为什么不直接从DataReader中获取它?
  • 同样关于样本:如果你返回一个无类型的DataTable,那么我猜你必须在调用代码中使用列名(SP调用返回的结果集)。这意味着如果我必须更改数据库中的某些内容,它可能会影响两个层。

我的建议(我尝试了两种方法 - 建议是我提出的最新工作方法 - 它随着时间的推移而发展)。

  • 为您键入的业务对象创建基类。
  • 将对象状态保留在基类中(新的,修改的等)
  • 将主要数据访问方法放在此类中,作为静态方法。只需一点努力(提示:泛型方法+ Activator.CreateInstance),您就可以为阅读器中返回的每一行创建一个业务对象。
  • 在业务对象中创建一个抽象方法,用于解析行数据(直接来自DataReader!)并填充对象。
  • 在派生的业务对象中创建静态方法,用于准备存储的proc参数(取决于各种过滤条件),并从基类调用通用数据访问方法。

目标是最终使用如下:

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

对我来说,好处是我只需更改一个文件就可以应对数据库列名,类型等的变化(一般情况下变化很小)。通过一些深思熟虑的区域,您可以组织代码,以便它们在同一对象中是独立的“层”:)。另一个好处是基类实际上可以从一个项目重用到另一个项目。并且代码膨胀是最小的(好吧,与好处相比。您还可以填充数据集并将它们绑定到UI控件:D

限制 - 每个域对象最终会有一个类(通常是每个主数据库表)。并且您无法在现有事务中加载对象(尽管您可以考虑传递事务,如果有的话)。

如果您对更多细节感兴趣,请告诉我 - 我可以稍微扩展一下答案。

答案 5 :(得分:1)

与我发布的here

类似
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界面的型号。 The connection object and reader is disposed only after the collection is enumerated once.

答案 6 :(得分:-1)

最简单的解决方案:

var dt=new DataTable();
dt.Load(myDataReader);
list<DataRow> dr=dt.AsEnumerable().ToList();