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 イベント ログにエラーとして現れます。

接続を開いたプリンシパルがその後新しいセキュリティ コンテキストを想定し、偽装されたセキュリティ コンテキストで接続をリセットしようとしたため、接続が切断されました。このシナリオはサポートされていません。Books Online の「偽装の概要」を参照してください。

...さらにコードの例外。

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 を発生させる前にそれを検出できると期待するのは妥当と思われます。

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 を呼び出したときにエラーが発生しています。接続プールは、最初に接続を使用するときにこれを呼び出しますが、その前に取得する方法はありません。

したがって、次の 4 つのオプションがあると思います。

  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's answerに基づいて、sp_reset_connection の「前に入る」方法として、接続文字列に「Connection Reset=False」を追加してみてください。(この設定の多くの欠点の説明については、「「汚れた」接続での作業」を参照してください。)

あなたの問題は、接続プールに関する既知の問題です。推奨される回避策は、接続プールを無効にすることです...これがデスクトップ アプリの場合は、代わりにいくつかの接続を開いたままにしておくことを検討する価値があるかもしれません (上記のリンク先の記事でも説明されています)。

最近 (SQL 2005 以降) の推奨事項 ( Application Roles and Connection Poolingの下) は、EXECUTE AS のように、「アプリケーション ロールの代わりに使用できる新しいセキュリティ メカニズムを利用する」ことです。

于 2011-10-13T17:59:19.693 に答える
0

すべての接続のプールをクリアする方法はありませんか。SqlPools.Clearか何か。

例外をキャッチして新しい接続を作成するだけで、プールに完全な新しい接続を作成させることができます。

于 2009-02-17T15:30:14.900 に答える
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

前回の質問への回答としても投稿しました。sp_setapprole を呼び出すときは、完了したら sp_unsetapprole を呼び出す必要があります。ここで提案した解決策が役に立ちます。

使用できないプールされた SqlConnection の検出


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.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 に答える
-1

アプリの役割を追加する前に、移動して実行してみてくださいsp_unsetapprole(本当に sproc の名前ですか? おそらく、sp_dropapprole正しい名前ですか?) 。setAppRole()

于 2009-02-17T12:18:14.533 に答える