如何在发生异常之前检测导致异常的条件?

时间:2009-02-17 11:58:09

标签: c# sql-server ado.net connection-pooling application-role

我对this question没有运气,所以我已经制作了这个简单的测试用例来证明这个问题。

在下面的代码中,在尝试使用连接之前是否可以检测到连接不可用?

    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

KABOOM在Windows事件日志中显示为错误;

  

连接已被删除,因为打开它的主体随后假定了一个新的安全上下文,然后尝试在其模拟的安全上下文下重置连接。不支持此方案。请参阅联机丛书中的“模拟概述”。

...加上代码中的异常。

setAppRole是一种在连接上设置应用程序角色的简单方法。它类似于......

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

在实际代码中,尝试在关闭连接之前使用 sp_unsetapprole ,但无法始终保证(继承错误的多线程应用程序)。在任何情况下,期望能够在导致它之前检测到kaboom似乎仍然是合理的。

8 个答案:

答案 0 :(得分:4)

简而言之,它看起来并不像你能做到的那样简单。

我的第一个想法是运行这个SQL:

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

如果您使用SQL Server Management Studio,则可以正常工作,但是当您从C#代码运行它时会失败。问题是当调用sp_setapprole时没有发生错误,它实际上是在连接池调用sp_reset_connection时发生的。连接池在您第一次使用连接时调用它,并且无法在它之前进入。

所以我想你有四个选择:

  1. 通过添加“Pooling = false”关闭连接池到您的连接字符串。
  2. 使用其他方式连接到SQL Server。有比ADO.Net更低级别的API,但坦率地说它可能不值得这么麻烦。
  3. 正如casperOne所说,您可以修复代码以正确关闭连接。
  4. 捕获异常并重置连接池。我不确定这会对其他开放式连接做些什么。示例代码如下:
  5. 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;
            }
        }
    }
    

答案 1 :(得分:1)

  

它实际上是在连接池调用时发生的   sp_reset_connection的。第一次使用时,连接池会调用此方法   一个连接,没有办法进入它。

基于Martin Brown's answer,您可以尝试将“Connection Reset = False”添加到连接字符串,作为“get in before”sp_reset_connection的方法。 (有关此设置的许多缺点的解释,请参阅“Working with “soiled” connections”。)

您的问题是known issue with connection pooling。建议的解决方法是禁用连接池...如果这是桌面应用程序,则可能值得考虑打开几个连接(也在the article linked above中解释)。

这些天(SQL 2005+)建议(在Application Roles and Connection Pooling下)是“利用您可以使用的new security mechanisms而不是应用程序角色”,例如EXECUTE AS。

答案 2 :(得分:0)

我不确定你的问题,但我认为如果你创建新的连接对象而不是重用它们,你会避免它。所以不要做

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

您将执行以下操作:

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

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

(请原谅伪代码......我的eeepc上的键盘是不可能使用的......)

答案 3 :(得分:0)

是否有办法清除所有连接池。 SqlPools.Clear等等。

您可以尝试捕获异常并创建一个新连接,这应该强制池创建一个完整的新连接。

答案 4 :(得分:0)

我还发布了这个以回应您之前的问题。在调用sp_setapprole时,你应该在完成后调用sp_unsetapprole,我在那里提出的解决方案将帮助你:

Detecting unusable pooled SqlConnections


看起来你正在调用sp_setapprole但是没有调用sp_unsetapprole然后让连接只返回池。

我建议使用一个结构(或类,如果你必须在方法中使用它)和IDisposable的实现,它将为你处理这个:

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. 您将创建与调用sp_setapprole存储过程相对应的SqlCommand。您可以生成cookie并将其存储在私有成员变量中。
  2. 这是您创建连接的位置。
  3. 客户端代码如下所示:

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

答案 5 :(得分:0)

您可以检查c.State(ConnectionState对象),它应该是以下之一:

System.Data.ConnectionState.Broken
System.Data.ConnectionState.Closed
System.Data.ConnectionState.Connecting
System.Data.ConnectionState.Executing
System.Data.ConnectionState.Fetching
System.Data.ConnectionState.Open

答案 6 :(得分:0)

@edg:你在评论中说,“......只有当它碰到实际的服务器并遇到安全问题时,如msg引用中所述。”

这指出了您的问题的根源:您 遇到安全问题,这似乎是不可避免的,因为调用代码假定的另一个标识不是用于打开连接的标识。这自然会产生安全日志条目。

由于身份更改是按设计进行的,因此解决方案可能是过滤安全日志。事件查看器具有“过滤器当前日志”操作,可以按关键字或偶数过滤。

+汤姆

答案 7 :(得分:-1)

尝试移动sp_unsetapprole(它真的是sproc的名称吗?可能sp_dropapprole是正确的吗?)到setAppRole()并在添加app角色之前执行它。< / p>