4

我对这个问题没有运气,所以我制作了这个尽可能简单的测试用例来证明这个问题。

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

    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,但不能始终保证(继承的有缺陷的多线程应用程序)。无论如何,期望能够在引起爆炸之前检测到爆炸似乎仍然是合理的。

4

8 回答 8

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. 捕获异常并重置连接池。不过,我不确定这会对其他打开的连接产生什么影响。下面的示例代码:
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;
        }
    }
}
于 2009-02-17T16:00:18.977 回答
1

它实际上是在连接池调用 sp_reset_connection 时发生的。当您第一次使用连接并且没有办法在它之前进入时,连接池会调用它。

根据Martin Brown 的回答,您可以尝试将“Connection Reset=False”添加到连接字符串中,作为“进入之前”sp_reset_connection 的一种方式。(有关此设置的许多缺点的说明,请参阅“使用“脏”连接”。)

Your problem is a known issue with connection pooling. The suggested work-around is to disable connection pooling... if this is a desktop app it may be worth considering holding a few connections open instead (also explained in the article linked above).

These days (SQL 2005+) the recommendation (under Application Roles and Connection Pooling) is to "take advantage of the new security mechanisms that you can use instead of application roles", like EXECUTE AS.

于 2011-10-13T17:59:19.693 回答
0

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

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

您将执行以下操作:

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

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

(请原谅伪代码...我eeepc上的键盘无法使用...)

于 2009-02-17T12:32:01.410 回答
0

没有办法清除所有连接的池。SqlPools.Clear 什么的。

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

于 2009-02-17T15:30:14.900 回答
0

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

检测不可用的池化 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. 这是您创建连接的地方。

客户端代码如下所示:

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.
}
于 2009-02-17T16:02:05.277 回答
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
于 2009-02-17T18:03:33.453 回答
0

@edg:您在评论中说,“......只有这样它才会击中实际的服务器并遇到味精引用中描述的安全问题”。

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

由于身份更改是设计使然,因此解决方案可能是过滤安全日志。事件查看器有一个过滤当前日志操作,可以按关键字或 eventid 过滤。

+汤姆

于 2009-02-17T20:56:55.220 回答
-1

在添加应用程序角色之前尝试移动sp_unsetapprole(它真的是存储过程的名称吗?可能sp_dropapprole是正确的名称?)并执行它。setAppRole()

于 2009-02-17T12:18:14.533 回答