Detecting unusable pooled SqlConnections

1.6k Views Asked by At

When I attempt to set an application role on a SqlConnection with sp_setapprole I sometimes get the following error in the Windows event log...

The connection has been dropped because the principal that opened it subsequently assumed a new security context, and then tried to reset the connection under its impersonated security context. This scenario is not supported. See "Impersonation Overview" in Books Online.)

... and a matching exception is thrown in my application.

These are pooled connections, and there was a time when connection pooling was incompatible with app roles - in fact the old advice from Microsoft was to disable connection pooling (!!) but with the introduction of sp_unsetapprole it is now (in theory) possible to clean a connection before returning it to the pool.

I believe these errors occur when (for reasons unknown) sp_unsetapprole is not run on the connection before it is closed and returned to the connection pool. sp_approle is then doomed to fail when this connection is returned from the pool.

I can catch and handle this exception but I would much prefer to detect the impending failure and avoid the exception (and messages in the event log) altogether.

Is it possible to detect the problem without causing the exception?

Thoughts or advice welcome.

4

There are 4 best solutions below

0
On BEST ANSWER

Nope, it's not possible.

1
On

It would seem that you are calling sp_setapprole but not calling sp_unsetapprole and then letting the connection just be returned to the pool.

I would suggest using a structure (or a class, if you have to use this across methods) with an implementation of IDisposable which will take care of this for you:

public struct ConnectionManager : IDisposable
{
    // The backing for the connection.
    private SqlConnection connection;

    // The connection.
    public SqlConnection Connection { get { return connection; } }

    public void Dispose()
    {
        // If there is no connection, get out.
        if (connection == null)
        {
            // Get out.
            return;
        }

        // Make sure connection is cleaned up.
        using (SqlConnection c = connection)
        {
            // See (1).  Create the command for sp_unsetapprole
            // and then execute.
            using (SqlCommand command = ...)
            {
                // Execute the command.
                command.ExecuteNonQuery();
            }
        }
    }

    public ConnectionManager Release()
    {
        // Create a copy to return.
        ConnectionManager retVal = this;

        // Set the connection to null.
        retVal.connection = null;

        // Return the copy.
        return retVal;        
    }

    public static ConnectionManager Create()
    {
        // Create the return value, use a using statement.
        using (ConnectionManager cm = new ConnectionManager())
        {
            // Create the connection and assign here.
            // See (2).
            cm.connection = ...

            // Create the command to call sp_setapprole here.
            using (SqlCommand command = ...)
            {
                // Execute the command.
                command.ExecuteNonQuery();

                // Return the connection, but call release
                // so the connection is still live on return.
                return cm.Release();
            }
        }
    }
}
  1. You will create the SqlCommand that corresponds to calling the sp_setapprole stored procedure. You can generate the cookie and store it in a private member variable as well.
  2. This is where you create your connection.

The client code then looks like this:

using (ConnectionManager cm = ConnectionManager.Create())
{
    // Get the SqlConnection for use.
    // No need for a using statement, when Dispose is
    // called on the connection manager, the connection will be
    // closed.
    SqlConnection connection = cm.Connection;

    // Use connection appropriately.
}
3
On

This is going on logic and not to much experience with using sp_setapprole but would it not be possible to check the security context before making the call? Or alternatively check the security permission and context first?

0
On

This is a bit dirty but if your original user has rights to VIEW SERVER STATE, select * from sys.sysprocesses will return all processes when the role is not active and a single row for the current process when it is.