如何改进这个可重用的方法类?

时间:2012-02-25 06:52:28

标签: c# asp.net oop concurrency

我有一个类,包含填充DropDowns,返回DataSet,返回Scalar或只是执行查询的方法。在StackOverflow中的older posts之一,我提交了同一类的错误代码。根据贡献者的建议,我改进了代码并想知道这个类是否适合在高并发环境中使用:

public sealed class reuse
{
    public void FillDropDownList(string Query, DropDownList DropDownName)
    {
        using (TransactionScope transactionScope = new TransactionScope())
        {
            using (SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["MyDbConnection"].ConnectionString.ToString()))
            {
                SqlDataReader dr;
                try
                {
                    if (DropDownName.Items.Count > 0)
                        DropDownName.Items.Clear();

                    SqlCommand cmd = new SqlCommand(Query, con);
                    dr = cmd.ExecuteReader();

                    while (dr.Read())
                        DropDownName.Items.Add(dr[0].ToString());

                    dr.Close();
                }
                catch (Exception ex)
                {
                    CustomErrorHandler.GetScript(HttpContext.Current.Response,ex.Message.ToString());
                }
            }
        }
    }
}

我想知道是否同时处理Command和DataReader对象,或者它们也会自动处理USING?

2 个答案:

答案 0 :(得分:3)

命令/阅读器:他们通过“使用”处理,但仅当你使用“使用”时,你应该这样做。

批评:

  • 你正在混合UI和数据访问 - 特别是异常处理没有给出调用代码的指示(虽然我个人也将控制代码分开),并假设调用者总是想要基于脚本的方法(对我来说,如果这段代码失败,那就非常错误了:让异常泡泡向上!)
  • 没有适当参数的机制;我怀疑的是,你是在连接字符串来进行查询 - SQL注入的潜在(但非常真实)风险
  • 你提到高并发;如果是这样,我希望看到一些缓存参与
  • 出于代码维护的原因,我将所有“创建连接”代码移到中心点 - “DRY”等;我不希望像这样的单个方法关注连接字符串来自
  • 的细节

坦率地说,我只是在这里使用短小精悍,并避免所有这些问题:

using(var connection = Config.OpenConnection()) {
     return connection.Query<string>(tsql, args).ToString();
}

(让调用者遍历列表,或使用AddRange或数据绑定,无论如何)

答案 1 :(得分:1)

一般同意Marc的回答,但我还有其他一些评论和不同的角度。希望我的回答对你有用。

首先,只要不需要任何状态信息且不共享数据,在并发环境中使用静态类和方法就没有错误。在你的情况下,填写DropDownList,它是完全正常的,因为你只需要一个字符串列表,一旦完成,你可以忘记所有你如何得到它。如果静态方法不访问任何静态字段,则对静态方法的并发调用之间也不会产生干扰。静态方法在.NET框架中很常见,并且它们是线程安全的。

在下面的示例中,我使用一个静态字段 - log4net记录器。它仍然是线程安全的,因为它不带任何状态,只是log4net库的跳转点,它本身就是线程安全的。建议至少查看log4net - 很棒的日志库。

如果您尝试从两个线程填充相同的下拉列表,那么它可能只是不安全,但如果此类不是静态的,那么它也将是不安全的。确保从一个(主)线程填充下拉列表。

返回您的代码。 混合UI和数据检索不是一种好的做法,因为它使代码的可维护性降低,稳定性降低。将这两者分开。 Dapper库可能是简化事物的好方法。我自己没有使用它,所以我能说的是它看起来非常方便和有效。如果你想/需要了解工作原理,请不要使用它。至少不是一开始。

在一个字符串中进行非参数化查询可能会导致SQL注入攻击,但如果该查询不是基于任何直接用户输入构建的,那么它应该是安全的。当然,你总是可以采用参数化来确定。

使用

处理异常
CustomErrorHandler.GetScript(HttpContext.Current.Response, ex.Message.ToString());

对于这个地方感觉片状和太复杂,可能会导致另一个例外。处理另一个异常时的异常意味着恐慌。我会把那个代码移到外面。如果你在这里需要一些东西,那就让它成为一个简单的log4net错误日志并重新抛出异常。

如果您只执行一次数据库读取,则不需要显式事务。 根据连接对象,它在任何情况下都不应该是静态的,并且可以按需创建。没有性能损失,因为.NET保留了一个准备使用连接池并回收那些'设置”。

我相信一个例子总是比解释更好,所以这里是我如何重新安排你的代码。

public static class reuse
{
    static public readonly log4net.ILog log = log4net.LogManager.GetLogger("GeneralLog");

    public static void FillDropDownList(string query, string[] parms,  DropDownList dropDown)
    {
        dropDown.Items.Clear();
        dropDown.DataSource = GetData(query, parms);
        dropDown.DataBind();
    }

    private static IEnumerable<string> GetData(string query, string[] parms)
    {
        using (SqlConnection con = new SqlConnection(GetConnString()))
        {
            try
            {
                List<string> result = new List<string>();
                SqlCommand cmd = new SqlCommand(query, con);
                cmd.Parameters.AddRange(parms);
                SqlDataReader dr = cmd.ExecuteReader();
                if (dr.VisibleFieldCount > 0)
                {
                    while (dr.Read())
                        result.Add(dr[0].ToString());
                }
                dr.Close();
                return result;
            }
            catch (Exception ex)
            {
                log.Error("Exception in GetData()", ex);
                throw;
            }
        }
    }

    private static string GetConnString()
    {
        return ConfigurationManager.ConnectionStrings["MyDbConnection"].ConnectionString.ToString(CultureInfo.InvariantCulture);
    }
}