views:

1019

answers:

5

i Dispose an SqlConnection object, but of of course it isn't really closed. i need closed connection to not hold locks on database objects. How can i prevent closed connections from holding locks?


Explanation of the above sentance for those who don't know:

  • When you close an ADO or ADO.NET connection, you aren't actually severing the connection to SQL Server. The ADO/ADO.NET infrastructure keeps the connection around in case you want to use it again. The connections are kept lingering around in what's called "The Connection Pool".

  • After a few minutes of not being used, the connection will be actually closed. Although, not really. TCP/IP has it's own method for keeping TCP connections open for a few more minutes (in the "CLOSE_WAIT" state). This is done in case you ask to open a TCP connection to the same IP:Port again. If so, it can use that already open TCP connection.

  • With connection pooling and SQL Server, the connection is still established to SQL Server. Each connection has a database context that it is sitting in. As long as a connection is sitting in that database: it holds a shared database (S-DB) lock on that database.

  • A Shared-Database lock simply means, "Don't delete this database while i'm in it please."

How can i prevent it from holding a shared lock on my database, while keeping the benefits of connection pooling?


My ad-hoc solution right now is every time a developer called Dispose:

connection.Dispose()

change it into a call to a global helper function:

Database.DisposeConnection(connection);

which changes the database context to master:

public static void DisposeConnection(SqlConnection connection)
{
    //Stop holding a database lock - in my database at least
    ADOHelper.ExecuteNonQuery(connection, "USE master");

    connection.Dispose();
}

It solves my immediate problem; closed connections aren't holding a lock on my database.

But now i'm worried that connection pooling will have its brain scrambled - because i switched database contexts behind its back.


In case anyone didn't know, or thought otherwise:

From the SDK:

Close and Dispose are functionally equivalent.

A: 

Reading the comment, you want to restore it.

OK, take it off-line.

Restore is not something that an app should be doing, so the DBA runs this before the RESTORE.

ALTER DATABASE foo SET OFFLINE WITH ROLLBACK IMMEDIATE
gbn
And what if the DBA is the 63 year old grandmother who wants her embroidery designs back? Unless you're offering to drive to her house and get her designs back after her grandson fixed her computer.
Ian Boyd
Does the DBA use an app to run the restore? Enterprise Manager perhaps? SSMS? osql?
Ian Boyd
connection.close won't help if you have other connections on the database, such as a maintenance job. But I doubt you have those based on your comments. Stick to .net
gbn
Won't have any maintenance jobs on an Express Edition, unless i wrote it. Which i didn't.
Ian Boyd
+2  A: 

Have you tried SqlClient.ClearPool?

From MSDN:

ClearPool clears the connection pool that is associated with the connection. If additional connections associated with connection are in use at the time of the call, they are marked appropriately and are discarded (instead of being returned to the pool) when Close is called on them.

Just call ClearPool on every connection, but you lose the pooling benefits if you do this.

public class DataFactory
{
  public SqlConnection CreateConnection(string connString)
  {
    SqlConnection conn = new SqlConnection(connString);
    SqlClient.ClearPool(conn);
    return conn;
   }
}

Alternatively, you can disable pooling all together using the Pooling property of the connection string:

string connString = "Data Source=MYSERVER;Initial Catalog=myDb;Integrated Security=true;Pooling=false"
scottm
Yeah, i do't really want to lose the benefits of connection pooling 100% of the time for a restore that might happen 0.0001% of the time.
Ian Boyd
Then your best bet when restoring the database is to put it in single user mode like others have said. You stop the process completely, so all connections are definitely closed.
scottm
+1  A: 

As you say when you Close or Dispose a connection it goes back into the pool but is not really closed.

What you then need to do in to close all connections in the pool. This can be done with a ClearAllPools command.

ClearAllPools resets (or empties) the connection pool. If there are connections in use at the time of the call, they are marked appropriately and will be discarded (instead of being returned to the pool) when Close is called on them.

http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlconnection.clearallpools%28VS.80%29.aspx

There is also a ClearPool command that does the same thing but for a single connection.

Hope this helps

Shiraz

Shiraz Bhaiji
+5  A: 

You can place the database in single user mode in order to restore it. IIRC, something like this...

ALTER DATABASE TheDatabaseToRestore SET SINGLE_USER WITH  ROLLBACK IMMEDIATE;
RESTORE DATABASE TheDatabaseToRestore 
FROM DISK =N'Z:\Path\To\Backup\BackupFile';
ALTER DATABASE TheDatabaseToRestore SET MULTI_USER;

see: Obtain Exclusive Access to Restore SQL Server and/or Restore Database Backup using SQL for some more background.

Edit: Single user mode is intended for backups and restores (which, on the non-express edition, is what I use it for regularly). Using it kicks out all other connections, and new connections cannot be made. I haven't played with the various "WITH" options such as with "ROLLBACK IMMEDIATE", but their usage seems straightforward.

Eamon Nerbonne
i haven't looked tested, but does that cause SQL Server to *close* all *closed* connections in the pool? If it does, this would be a very good solution.
Ian Boyd
Yes, the WITH ROLLBACK IMMEDIATE clause kicks out all current users immediately. I think you will need to implement this connection with a separate SQL login for administration, though. Otherwise, all your connections still meet the single user criteria and will not be closed.
Jerry Bullard
Single user mode is more accurately thought of as single connection mode - you don't need a separate admin login; using this will kick out all other connections by the same user as well. Watch out that you're the one that gets to keep the connection - I'm not sure when it happens, but I've seen a random connection claim the sole connection and then it's a hassle reconnecting (you'll need to kill the connection).
Eamon Nerbonne
You can use the more gentle WITH ROLLBACK AFTER *N* SECONDS. Note that the connection pool hates this and the application will get connection errors when attempting to connect (which it shouldn't the pool should recover gracefully... but it doesn't)
Remus Rusanu
The connection pool breaks with rollback after N seconds? Weird - I use the immediate disconnect option; it recovers from that just fine.
Eamon Nerbonne
Accepted. Even though this wasn't the answer to the Stackoverflow question, it does solve my problem. The other answer, confirming my `use master` workaround i presume is the best answer to the *actual* question.
Ian Boyd
+1  A: 

Execute "USE TempDB;" first. Then close it.

Or "USE master;", if that's more to your liking.

S-DB locks on either of those databases shouldn't matter during normal production operation, as you cannot get rid of either one and continue running the Server anyway.

RBarryYoung
Which is what i'm doing now
Ian Boyd
This is essentially a "co-answer". This is the actual answer to my question. On the other hand i accepted the answer that solves my immediate problem.
Ian Boyd
Heh. Understood, I get that a lot here at SO.
RBarryYoung