5

是否存在需要以下结构的情况?

using (Something something = new Something())
{
    try
    {
    }
    finally
    {
        something.SomeCleanup();
    }
}

或者,是否应该在隐式调用中执行所有清理任务?something.Dispose()


这是有问题的代码:

public static DataTable GetDataTable(string cmdText, IEnumerable<Parameter> parameters)
{
    // Create an empty memory table.
    DataTable dataTable = new DataTable();

    // Open a connection to the database.
    using (SqlConnection connection = new SqlConnection(ConfigurationTool.ConnectionString))
    {
        connection.Open();

        // Specify the stored procedure call and its parameters.
        using (SqlCommand command = new SqlCommand(cmdText, connection))
        {
            command.CommandType = CommandType.StoredProcedure;

            SqlParameterCollection parameterCollection = command.Parameters;
            foreach (Parameter parameter in parameters)
                parameterCollection.Add(parameter.SqlParameter);

            try
            {
                // Execute the stored procedure and retrieve the results in the table.
                using (SqlDataAdapter dataAdapter = new SqlDataAdapter(command))
                    try
                    {
                        dataAdapter.Fill(dataTable);
                    }
                    catch
                    {
                        dataTable.Dispose();
                        dataTable = null;
                    }
            }
            finally
            {
                //parameterCollection.Clear();
            }
        }
    }

    return dataTable;
}

注意:我已经定义了这个Parameter类,所以这个函数的用户不必SqlParameter直接处理 s 的创建。类的SqlParameter属性Parameter可用于检索SqlParameter.

在某些时候,我的程序执行以下操作(无法发布代码,因为它涉及很多类;基本上,我有一个创建大量对象的迷你框架):

  1. 创建一个 s 数组Parameter
  2. GetDataTable('sp_one', parameters).
  3. GetDataTable('sp_two', parameters).
4

3 回答 3

5

using关键字调用.Dispose()方法。如果您在 IDisposable 对象的 dispose 方法之外进行了必要的清理,那么您将需要在它自己的 finally 块中执行此操作。这带来了两点:

  1. 此时,您可能会争辩说,您不妨跳过 using 块,也只在 finally 块中调用 Dispose()。就个人而言,我仍然会选择一个using街区。为您的 IDisposable 实例始终拥有一个只是一个好习惯。
  2. 我谦虚地建议,如果您满足上述条件,则需要重新设计您的类以利用 IDisposable 模式。

根据您发布的代码,问题是您的参数仍然植根于某个地方(也许您正在重新使用它们?)。因为参数仍然是 root 的,所以无法收集它们。它们还包含对它们所附加的命令的引用,因此您的 SqlCommand 对象也不能立即被收集,因为现在它仍然是根目录。

关键是 .Net 框架为非托管资源保留了 Dispose()模式。因为 SqlParameters 和 SqlParameterCollection 是托管类型,所以在收集它们之前不会触及它们,这与处置完全分开。当您的 SqlCommand 最终被收集时,它的 SqlParameter 集合也将被处理。只是不要混淆收集、处理和它们的目的。

您要做的是在添加每个参数时对其进行复制,而不是将现有参数添加到集合中。

public static DataTable GetDataTable(string cmdText, IEnumerable<Parameter> parameters)
{
    // Create an empty memory table.
    DataTable dataTable = new DataTable();

    // Prepare a connection to the database and command to execute.
    using (SqlConnection connection = new SqlConnection(ConfigurationTool.ConnectionString))
    using (SqlCommand command = new SqlCommand(cmdText, connection))
    {
        command.CommandType = CommandType.StoredProcedure;

        SqlParameterCollection parameterCollection = command.Parameters;
        foreach (Parameter parameter in parameters)
            parameterCollection.Add(CloneParameter(parameter.SqlParameter));

        // Execute the stored procedure and retrieve the results in the table.
        using (SqlDataAdapter dataAdapter = new SqlDataAdapter(command))
        {
             dataAdapter.Fill(dataTable);
        }
    }

    return dataTable;
}

这里需要注意的一些事情:我能够摆脱你所有的尝试块。一个都不需要。此外,SqlDataAdapter.Fill() 方法将为您打开和关闭连接,因此您不需要该部分。

现在让我们谈谈那个 CloneParameter() 函数。我觉得你觉得它违背了代码的目的,即尝试重用参数。我向你保证,在这里重用参数是个坏主意。性能损失可以忽略不计,尤其是与存储过程执行相比。我把 CloneParameter() 的实现留给了你,原因有两个:首先它很简单,其次是我们已经超出了我的正常数据访问模式。我通常添加参数的做法是接受一个 Action<SqlParameterCollection> 委托,而不是一个可枚举的参数。该函数的声明更像这样:

public IEnumerable<IDataRecord>GetData(string cmdText, Action<SqlParameterCollection> addParameters)

并被称为:

foreach(var record in GetData("myprocedurename", p => 
  {
      p.Add( /*new parameter here*/ );
      p.Add( /*new parameter here*/ );
    //...
  })
 .Select( /*Returning a IEnumerable rather than a datatable allows me to use it with linq to objects.*/
          /* For example, you could use this spot to convert from DataRecords returned by ADO.Net to business objects */ 
        ))
{
   // use the results here...
}

由于您要连续填充两个表,听起来您有一些工作要做客户端,这可能证明与 DataReader/IEnumerable 方法相比是合理的,但我确实想提一下,因为大多数时候您的代码都基于DataReader 是更好的选择。

在您的情况下,我将使用我现有的基于 Action-delegate 的模式并希望尽可能多地重复使用一组重复的参数,我会做一个真正的命名方法,该方法知道如何添加参数并匹配我的 Action 委托. 然后我可以将该方法传入,并获得所需的参数重用。

于 2011-03-31T14:32:09.503 回答
4

有趣的问题!

这一切都取决于你的Something班级。如果它设计不佳并且需要多阶段清理,它会将其特质强加给客户。

您不应该将课程设计成那样。如果您有临时清理工作要做,请将它们封装在自己的类中并使用如下代码:

using (Something something = new Something()) {
  // ...
  using (SomethingElse somethingElse = something.GiveMeSomethingElse()) {
  }
  // ...
} 

更新:

对于您的示例,它可能如下所示:

using (SqlConnection connection = new SqlConnection(connectionString)) {
  connection.Open();

  using (SqlCommand command = new SqlCommand("select * from MyTable where id = @id", connection)) {

    // to "reuse" the parameters collection population, just extract this to a separate method      
    command.Parameters.Add(new SqlParameter("id", id));

    // ... execute the command

  }

}

更新 2:

只需这样做:

GetDataTable('sp_one', CreateParameters());
GetDataTable('sp_two', CreateParameters());
于 2011-03-31T14:33:59.303 回答
1

Dispose 应该清理所有非托管资源。有另一种清理方法,例如执行一些功能或数据库操作,是完全可能的。

于 2011-03-31T14:32:20.443 回答