5

Is there any case in which the following structure is needed?

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

Or, should all cleanup tasks be performed in the implicit something.Dispose() call?


Here is the offending code:

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;
}

NOTE: I have defined the Parameter class so users of this function don't have to deal with the creation of SqlParameters directly. The SqlParameter property of the Parameter class can be used to retrieve an SqlParameter.

At some point, my program does the following (cannot post the code, because it involves a lot of classes; basically, I have a mini-framework creating lots of objects):

  1. Create an array of Parameters.
  2. GetDataTable('sp_one', parameters).
  3. GetDataTable('sp_two', parameters).
isekaijin
  • 19,076
  • 18
  • 85
  • 153
  • Did you create the Something class? If so, why didn't you put all cleanup operations in the Dispose method? – mbeckish Mar 31 '11 at 14:35
  • @mbeckish: I did not create the `Something` class. Actually, my `Something` classes are Microsoft's own `SqlConnection` and `SqlParameter`. Common sense says Microsoft programmers would be intelligent enough to close open database connections in the `Dispose` method (which, of course, does not mean they should not provide the `Close` method as well), but the documentation says nothing. I really miss using C++ and having access to the STL's source code. – isekaijin Mar 31 '11 at 14:38
  • I'm pretty sure that `SqlConnection.Dispose` does the same what `Close` does (except for being able to reopen after `Close` as opposed to `Dispose`). If your connections are not actually closed, it may be due to connection pooling. Btw, you actually have access to the source code, it has been released, also you can use Reflector http://reflector.red-gate.com/download.aspx?TreatAsUpdate=1 – František Žiačik Mar 31 '11 at 14:55
  • @František Žiačik: I didn't know `SqlConnection.Dispose` actually closed the connection, so that's my fault for being ignorant. But I am quite sure I got an "Another SqlParameterCollection contains the SqlParameter" error when I tried adding a SqlParameter to a SqlParameterCollection for a second time, even when the first `SqlParameterCollection` belonged to an already disposed `SqlCommand`. – isekaijin Mar 31 '11 at 15:01
  • why don't you just create new SqlParameter? This is just a matter of design decision in which somebody decided that an SqlParameter can only be added to a single SqlParameterCollection (I don't know why) and has nothing to do with Dispose pattern. You don't need to worry about memory, that's where Garbage Collector will help. – František Žiačik Mar 31 '11 at 15:17

3 Answers3

5

The using keyword only calls the .Dispose() method. If you have necessary clean up that happens outside of the dispose method on an IDisposable object, then you will need to do that in it's own finally block. This bring up two points:

  1. At this point, you could argue that you might as well skip the using block and just call Dispose() inside the finally block, too. Personally, I'd still go with a using block. It's just a good habit to be in to always have one for your IDisposable instances.
  2. I humbly suggest that if you meed the conditions above, you need to re-design your class to take advantage of the IDisposable pattern.

Based on the code you posted, the problem is that your parameters are still rooted somewhere (perhaps you're re-using them?). Because the parameters are still rooted they cannot be collected. They also contain a reference to the command they are attached to, so your SqlCommand object cannot be collected right away either, because now it's still rooted as well.

The key point is that the .Net framework reserves the Dispose() pattern for unamanaged resources. Because SqlParameters and SqlParameterCollection are managed types, they are not touched until they are collected, which happens completely separately from disposal. When your SqlCommand is finally collected, it's SqlParameter collection will be taken care of as well. Just don't confuse collection, disposal, and their purposes.

What you want to do is make a copy of each parameter when you add it, rather than add the existing parameter to the collection.

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;
}

Some things to note here: I was able to get rid of all your try blocks. Not one of them was needed. Also, the SqlDataAdapter.Fill() method will open and close the connection for you, so you don't need that part.

Now let's talk about that CloneParameter() function. I get the impression you feel like it defeats the purpose of your code, which is to try to re-use parameters. I promise you that parameter re-use here is a bad idea. The performance penalty is negligible to the point of non-existence, especially compared to the storedprocedure execution. I left the CloneParameter() implementation to you, for two reason: first of all it's trivial, and second is that we're already outside of my normal data access pattern. What I normally do to add parameters is accept an Action<SqlParameterCollection> delegate, rather than a parameter enumerable. The function is declared more like this:

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

and is called like this:

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...
}

Since you're filling two tables in a row, it sounds like you have some work to do client side that may justify that vs the DataReader/IEnumerable approach, but I do want to mention this, as most of the time basing your code on a DataReader is the better option.

What I would do in your case with my existing Action-delegate-based pattern and wanting to re-use a duplicate set of parameters as much as possible is have a real, named method that knows how to add the parameters and matches my Action delegate. Then I could just pass that method in, and get the desired parameter re-use.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • @Joel: Actually, `Something` is none other than Microsoft's own `SqlCommand`, which apparently does not clear its parameter collection when disposed. – isekaijin Mar 31 '11 at 14:48
  • @Eduardo - parameters are _managed_ resources that can be cleaned up by the garbage collector. There is no need to call .Clear() on the parameter collection. The _only_ times you might ever have a problem here is you're doing something naughty such that the parameter collection is still rooted or if you have very large value stored in the parameter, such that the values end up on the Large Object Heap, and even in this case just clearing the parameter collection won't help. – Joel Coehoorn Mar 31 '11 at 14:50
  • @Eduardo - what I will give you is SqlDataReader, which is not closed by it's Dispose() method, but even there it's only a problem if you're relying on CommandBehavior.CloseConnection – Joel Coehoorn Mar 31 '11 at 14:54
  • @Eduardo - Okay, updated. The important part is the first paragraph after the break. That explains what's going on. – Joel Coehoorn Mar 31 '11 at 15:45
  • @Joel: How do you conclude that `SqlDataReader` isn't closed when disposed? The `Dispose` method definitely ends up calling `Close`. Or are you saying that the `Close` method itself doesn't actually close the reader? – LukeH Mar 31 '11 at 20:21
  • @LukeH - you're right. it used to be broken that way, but it looks like more recent versions of the framework fix this. – Joel Coehoorn Mar 31 '11 at 20:43
4

Interesting question!

It all depends on your Something class. If it was poorly designed and it needs multi-stage cleanup, it forces its idiosyncracies to its clients.

You shouldn't design classes to be like that. If you have interim cleanups to do, encapsulate them in their own classes and use code like this:

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

UPDATE:

For your example, it might look like this:

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

  }

}

UPDATE 2:

Just do this:

GetDataTable('sp_one', CreateParameters());
GetDataTable('sp_two', CreateParameters());
Jordão
  • 55,340
  • 13
  • 112
  • 144
  • I have a function whose parameters are a stored procedure's name and an array of SqlParameters. Those parameters could be reused in several Stored Procedure calls. Unfortunately, `SqlCommand` apparently does not clear its own parameter collection when disposed. – isekaijin Mar 31 '11 at 14:50
  • Well then, you can recreate the parameters for each call to the method. What you reuse is the code, not the runtime array of parameters. – Jordão Mar 31 '11 at 15:11
  • While the typical `SqlParameter` object is not a particularly heavyweight object, why create more of them if it is unnecessary? – isekaijin Mar 31 '11 at 15:23
  • Is it a _real_ bottleneck? I think that with database calls in the mix, you really don't need to think about optimizing this aspect of your application. – Jordão Mar 31 '11 at 15:29
  • I think the problem here is you are still thinking C++ where you should be thinking C#. :) That would mean leaving it to Garbage Collector to deal with it. – František Žiačik Mar 31 '11 at 15:34
1

Dispose should clean up all your unmanaged resources. Having another cleanup method, to perform some functional or database actions for example, is perfectly possible.

Gerrie Schenck
  • 22,148
  • 20
  • 68
  • 95