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

本文关键字:方法 何改进 | 更新日期: 2023-09-27 18:25:41

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

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自动处理?

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

命令/读取器:they将通过"using"来处理,但仅当您对它们使用"usinng"时,您才应该这样做。

批评:

  • 你把UI和数据访问混合得很糟糕——特别是异常处理没有给调用代码任何指示(尽管我个人也会把控制代码分开),并假设调用方总是想要基于脚本的方法(对我来说,如果这个代码失败了,那就大错特错了:让异常向上冒泡!)
  • 没有适当参数的机制;因此,我的怀疑是,您正在连接字符串,以产生SQL注入的潜在(但非常真实)查询风险
  • 你提到高并发;如果是这样的话,我希望在这里看到一些缓存参与
  • 出于代码维护的原因,我会将所有"创建连接"代码移到一个中心点——"DRY"等;我不希望像这样的单个方法关心连接字符串的来源等细节

坦率地说,我只想在这里使用dapper,并避免所有这些问题:

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

(并让调用者在列表上迭代,或者使用AddRange或数据绑定,不管怎样)

总体上同意Marc的回答,但我有一些其他意见和不同的角度。希望我的回答对你有用。

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

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

如果您试图从两个线程中填充相同的下拉列表,这可能是不安全的,但如果这个类不是静态的,这也会是不安全。确保下拉列表是从一个(主)线程填充的。

回到你的代码混合UI和数据检索不是一个好的做法,因为这会降低代码的可维护性和稳定性。把这两个分开。Dapper库可能是简化事物的好方法。我自己没有用过,所以我只能说它看起来非常方便和高效。如果你想/需要学习东西是如何工作的,不要使用它。至少一开始不是这样。

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

使用处理异常

CustomErrorHandler.GetScript(HttpContext.Current.Response, ex.Message.ToString());

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

如果只进行一次DB读取,则不需要显式事务根据连接对象,它在任何情况下都不应该是静态的,而是根据需要创建的这不会对性能造成影响,因为.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);
    }
}