0

C# アプリケーションで 2008 R2 の SQL Server Broker を使用しており、SQL Server が有害なメッセージを検出してターゲット キューを無効にしたケースを処理しようとしています。

この場合、メッセージを受信しようとすると SqlException がスローされます。その時点で、私が使用している SqlTransaction はコミットできなくなったようです。

このチュートリアルを使用して、私の C# コードと共にデモを行います。

最初に、チュートリアルの T-SQL コードを使用して必要なサービス ブローカー オブジェクトを作成し、メッセージを送信してターゲット キューに配置します。

CREATE MESSAGE TYPE
       [//AWDB/1DBSample/RequestMessage]
       VALIDATION = WELL_FORMED_XML;
CREATE MESSAGE TYPE
       [//AWDB/1DBSample/ReplyMessage]
       VALIDATION = WELL_FORMED_XML;
GO

CREATE CONTRACT [//AWDB/1DBSample/SampleContract]
      ([//AWDB/1DBSample/RequestMessage]
       SENT BY INITIATOR,
       [//AWDB/1DBSample/ReplyMessage]
       SENT BY TARGET
      );
GO

CREATE QUEUE TargetQueue1DB;

CREATE SERVICE
       [//AWDB/1DBSample/TargetService]
       ON QUEUE TargetQueue1DB
       ([//AWDB/1DBSample/SampleContract]);
GO

CREATE QUEUE InitiatorQueue1DB;

CREATE SERVICE
       [//AWDB/1DBSample/InitiatorService]
       ON QUEUE InitiatorQueue1DB;
GO

DECLARE @InitDlgHandle UNIQUEIDENTIFIER;
DECLARE @RequestMsg NVARCHAR(100);

BEGIN TRANSACTION;

BEGIN DIALOG @InitDlgHandle
     FROM SERVICE
      [//AWDB/1DBSample/InitiatorService]
     TO SERVICE
      N'//AWDB/1DBSample/TargetService'
     ON CONTRACT
      [//AWDB/1DBSample/SampleContract]
     WITH
         ENCRYPTION = OFF;

SELECT @RequestMsg =
       N'<RequestMsg>Message for Target service.</RequestMsg>';

SEND ON CONVERSATION @InitDlgHandle
     MESSAGE TYPE 
     [//AWDB/1DBSample/RequestMessage]
     (@RequestMsg);

SELECT @RequestMsg AS SentRequestMsg;

COMMIT TRANSACTION;
GO

次に、コンソール アプリケーションであるこの C# コードを実行します。

using System.Data.SqlClient;

namespace ServerConsoleApplication
{
    class Program
    {
        static SqlConnection conn = null;

        static void Main(string[] args)
        {
            conn = new SqlConnection("connection string");
            conn.Open();

            Receive(); // 1
            Receive(); // 2
            Receive(); // 3
            Receive(); // 4
            Receive(); // 5
            Receive(); // 6 - Poison Message exception invoked

            conn.Close();
        }

        static void Receive()
        {
            using (SqlTransaction tran = conn.BeginTransaction())
            {
                try
                {
                    using (SqlCommand waitCommand = conn.CreateCommand())
                    {
                        waitCommand.Transaction = tran;
                        waitCommand.CommandText = string.Format("WAITFOR (RECEIVE TOP (1) conversation_handle, convert(xml,message_body) FROM TargetQueue1DB), TIMEOUT 1000");

                        using (SqlDataReader reader = waitCommand.ExecuteReader())
                        {
                        }
                    }

                    // Rollback on purpose to cause the poison message
                    tran.Rollback();
                }
                catch (SqlException ex)
                {
                    if (ex.Number == 9617)
                    {
                        // Re-Enable the queue
                        using (SqlCommand enableCmd = conn.CreateCommand())
                        {
                            enableCmd.Transaction = tran;
                            enableCmd.CommandText = string.Format(@"ALTER QUEUE TargetQueue1DB WITH STATUS = ON");
                            enableCmd.ExecuteNonQuery();
                        }

                        System.Data.SqlTypes.SqlGuid handle = System.Data.SqlTypes.SqlGuid.Null;

                        // Pull the poison message off the queue
                        using (SqlCommand waitCommand = conn.CreateCommand())
                        {
                            waitCommand.Transaction = tran;
                            waitCommand.CommandText = string.Format("WAITFOR (RECEIVE TOP (1) conversation_handle, convert(xml,message_body) FROM TargetQueue1DB), TIMEOUT 1000");

                            using (SqlDataReader reader = waitCommand.ExecuteReader())
                            {
                                while (reader.Read())
                                {
                                    handle = reader.GetSqlGuid(0);
                                }
                            }
                        }

                        // End the conversation just for clean up
                        using (SqlCommand endCmd = conn.CreateCommand())
                        {
                            endCmd.Transaction = tran;
                            endCmd.CommandText = "End Conversation @handle";
                            endCmd.Parameters.Add("@handle", System.Data.SqlDbType.UniqueIdentifier);
                            endCmd.Parameters["@handle"].Value = handle;
                            endCmd.ExecuteNonQuery();
                        }

                        // Commit the transaction so the message is removed from queue.
                        tran.Commit();
                    }
                }
            }
        }
    }
}

上記のコードは、動作のデモンストレーションにすぎません。もちろん、通常このように Rollback を受け取って呼び出すことはありません。

Receive メソッドはメッセージを受信し、トランザクションで Rollback を呼び出して、有害なメッセージの動作を刺激します。Receive への 6 回目の呼び出しで、期待どおりにキューが無効になっているため、SQLException がスローされます。

この時点で、キューを再度有効にし、有害なメッセージを取り除き、会話を終了したいと思います (終了は必要ありません)。これはすべて機能しますが、その有害なメッセージをキューから外したいので、トランザクションをコミットします。

結果 コミット呼び出しで例外がスローされます。

この SqlTransaction は完了しました。使用できなくなります。

Receive の 6 回目の呼び出しで Rollback または Commit が呼び出されずに、このトランザクションがどのように完了したのでしょうか?

また、TargetQueue1DB のメッセージはどのように削除されたのですか? コミットされたトランザクション内にない限り、受信はキューからメッセージを削除しないと思いました。ただし、コミットが呼び出される前に TargetQueue1DB を見ると、キューは空です。

SqlException がキャッチされたときに waitCommand がスコープ内にあるようにコードを少し変更すると、waitCommand インスタンスの Connection プロパティと Transaction プロパティが null に設定されていることがわかります。これは私にとって奇妙な振る舞いです。

4

1 に答える 1

2

SqlTransaction のクライアント状態は、必ずしもサーバー上のトランザクション状態を反映しているわけではありません。キャッチした例外が 1205、デッドロックであるかどうかを検討してください。このような場合、現在のフレームにコミットもロールバックもされていない SqlTransaction オブジェクトがある場合でも、サーバーで例外が発生する前に、トランザクションはサーバーで既にロールバックされています。

catch ブロックでは、現在のトランザクション オブジェクトを破棄し、エラー処理ロジックを実行するために新しいトランザクション オブジェクトを開始する必要があります。

サーバーで実際のトランザクションを開始せずにキャッチ処理ロジックを実行したため、メッセージは削除されました。tranもはや関係のない有効期限切れのオブジェクトを使用しました。RECEIVE はすぐにコミットされました (周囲のトランザクションはまったくありません)。

于 2014-08-26T21:13:38.693 に答える