views:

873

answers:

7

I had no luck with this question so I've produced this simple-as-possible-test-case to demonstrate the problem.

In the code below, is it possible to detect that the connection is unusable before trying to use it?

    SqlConnection c = new SqlConnection(myConnString);

    c.Open();  // creates pool

    setAppRole(c);  // OK

    c.Close(); // returns connection to pool

    c = new SqlConnection(myConnString); // gets connection from pool

    c.Open(); // ok... but wait for it...

    // ??? How to detect KABOOM before it happens?

    setAppRole(c); // KABOOM

The KABOOM manifests as a 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.

...plus an exception in code.

setAppRole is a simple method to set an application role on the connection. It is similar to this...

static void setAppRole(SqlConnection conn) {

    using (IDbCommand cmd = conn.CreateCommand())
        {
            cmd.CommandType = CommandType.Text;
            cmd.CommandText = "exec sp_setapprole ";
            cmd.CommandText += string.Format("@rolename='{0}'",myUser);
            cmd.CommandText += string.Format(",@password='{0}'",myPassword);
            cmd.ExecuteNonQuery();
        }
    }

In the real code an attempt is made to use sp_unsetapprole prior to closing the connection but it cannot always be guaranteed (inherited buggy multithreaded app). In any case it still seems reasonable to expect to be able to detect the kaboom before causing it.

A: 

Try moving sp_unsetapprole (is it really the name of the sproc? Probably, sp_dropapprole is the correct one?) to setAppRole() and execute it prior to adding an app role.

Anton Gogolev
LOL - sp_dropapprole would remove the application role from the database altogether
Ed Guiness
Sadly it isn't possible to unset an approle before setting it. :( sp_unsetapprole requires a cookie that is returned from sp_setapprole (cookie code not shown to keep demo simple)
Ed Guiness
A: 

I'm not sure about your problem, but I think you would avoid it if you created new connection objects instead of reusing them. So instead of doing

c.Open();
blabla;
c.Close();
c.Open(); 
kaboom...

You would do the following:

using (new SqlConnection ...)
{
  c.Open();
  blabla;
}

using (new SqlConnection ... )
{
  c.Open();
  no kaboom?
}

(Please forgive the pseudocode... The keyboard on my eeepc is impossible to use...)

Rune Grimstad
A worthy thought, but in fact I do get a new SqlConnection each time. The connection pool doesn't care, it returns the old connection that was closed (which normally is fine, and expected). I cut out the extra code to get new SqlConnection() for this demo.
Ed Guiness
I've updated the demo code above to show c = new SqlConnection(), I figured it doesn't complicate things too much to show it.
Ed Guiness
A: 

Isn't there a way to clear a pool of all connections. SqlPools.Clear or something.

You could simply try and catch the exception and create a new connection, that should force the pool to create a complete new connection.

Barfieldmv
Yep, I could clear the pool, but that kind of kills the benefit of having the pool in the first place :-)
Ed Guiness
In the real code I do in fact catch the exception, but that doesn't prevent error messages in the event log, which is what I'm trying to achieve. (after catching an exception I can explicitly Close() and Open() to get a new connection from the pool. This works ok but leaves the event log problem.
Ed Guiness
+2  A: 

In short, it doesn't look like you can in any simple way.

My first thought was to run this SQL:

SELECT CASE WHEN USER = 'MyAppRole' THEN 1 ELSE 0 END

This works if you use SQL Server Management Studio, but fails when you run it from C# code. The trouble is the error you are getting is not occuring when the call to sp_setapprole is made, it is actually occuring when connection pooling calls sp_reset_connection. Connection pooling calls this when you first use a connection and there is no way to get in before it.

So I guess you have four options:

  1. Turn connection pooling off by adding "Pooling=false;" to your connection string.
  2. Use some other way to connect to SQL Server. There are lower level APIs than ADO.Net, but frankly it is probably not worth the trouble.
  3. As casperOne says you could fix your code to close the connection correctly.
  4. Catch the exception and reset the connection pool. I'm not sure what this will do to other open connections though. Example code below:
class Program
{
    static void Main(string[] args)
    {
        SqlConnection conn = new SqlConnection("Server=(local);Database=Test;UID=Scrap;PWD=password;");

        setAppRole(conn);
        conn.Close();

        setAppRole(conn);
        conn.Close();
    }

    static void setAppRole(SqlConnection conn) 
    {
        for (int i = 0; i < 2; i++)
        {
            conn.Open();
            try
            {
                using (IDbCommand cmd = conn.CreateCommand())
                {
                    cmd.CommandType = CommandType.Text;
                    cmd.CommandText = "exec sp_setapprole ";
                    cmd.CommandText += string.Format("@rolename='{0}'", "MyAppRole");
                    cmd.CommandText += string.Format(",@password='{0}'", "password1");
                    cmd.ExecuteNonQuery();
                }
            }
            catch (SqlException ex)
            {
                if (i == 0 && ex.Number == 0)
                {
                    conn.Close();
                    SqlConnection.ClearPool(conn);
                    continue;
                }
                else
                {
                    throw;
                }
            }
            return;
        }
    }
}
Martin Brown
A: 

I've also posted this in response to your previous question. When calling sp_setapprole, you should call sp_unsetapprole when you are done, and the solution I proposed there will help you:

http://stackoverflow.com/questions/553331/detecting-unusable-pooled-sqlconnections/557515#557515


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.
}
casperOne
Thanks, friendly ghost, I do call unsetapprole, this question is about whether its possible to detect the scenario where it hasn't been called.
Ed Guiness
@edg: I don't believe that you can detect it. I can't seem to find a SP which will return the current role on the connection, which is what you want.The structure above should work in your app to deterministically remove the role. Multi-threading should have nothing to do with it.
casperOne
@edg: I say multi-threading has nothing to do with it, because you aren't sharing connections between threads, right? If you are, that's a situation you have to clean up. Also, your code doesn't guarantee a call to sp_unsetapprole on release, while what I provided does.
casperOne
no, not sharing connections between threads, and we have code to unset approle that to best of our knowledge always runs appropriately. Debugging the app isn't really a question that SO can answer while **detecting** the failing condition possibly is.
Ed Guiness
@edg: I think you are putting the cart before the horse, since you won't be able to catch the exception. You can't detect what the app role is on a connection, there is no query/sp to do that.
casperOne
@edg: With that in mind, the code you posted shows that if an exception occurs while you are using the connection with the role, you are NOT cleaning up, unless there is a try/catch or structure similar to mine that you are using. Without that, you are leaving the role on the connection.
casperOne
We **CAN** catch the exception. The code posted is a **DEMO** to illustrate the scenario I want to detect. Thanks anyway.
Ed Guiness
@edg: I think you are missing the point here. You ^can't^ find out what the app role is. And you ^can't^ figure out what exception the code is going to throw before you throw it (unless you duplicate the code). Also, the error indicates that something is not being cleaned up correctly.
casperOne
@edg: Given all that, you should try to address the problem, instead of the symptom. Trying to figure out an improper state on the connection is the symptom. Figuring out why the connection state is wrong is addressing the problem, which is what my solution, and others, are trying to get at.
casperOne
A: 

You could check c.State (ConnectionState object), which should be one of the following:

System.Data.ConnectionState.Broken
System.Data.ConnectionState.Closed
System.Data.ConnectionState.Connecting
System.Data.ConnectionState.Executing
System.Data.ConnectionState.Fetching
System.Data.ConnectionState.Open
Chris Doggett
ConnectionState would always be Open in my case. There's no problem opening and closing, just when the connection is used. It is only when the pooled connection is re-used that the problem occurs, it is only then it hits the actual server and encounters a security prob as described in the msg quote
Ed Guiness
A: 

@edg: You say in a comment, "...it is only then it hits the actual server and encounters a security prob as described in the msg quote".

This points to the source of your problem: You are encountering a security problem, and this appear unavoidable because the calling code assumes another identity than was used to open the connection. This naturally makes a security log entry.

Since the identity change is by design, perhaps the solution is to filter the securty log. The event viewer has a Filter Current Log action that can filter by keyword or eventid.

+tom

Tom A
thanks Tom, if I could channel these events away from the application log and into a custom source (e.g. MyAppLog instead of Application) that would probably solve my problem. Unfortunately these event log messages are not controllable from the .Net layer via C# - they belong to SQL Server.
Ed Guiness
(Filtering helps after the event has been recorded, I need to prevent these messages or channel them elsewhere)
Ed Guiness
ic. Hmmm, many server products use the provider model where there might be a logging provider. If SQL Server offered that then you could insert a filter as a custom error provider. A quick search shows Integration services has this. but i don't know about SQL Server itself.
Tom A