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 に設定されていることがわかります。これは私にとって奇妙な振る舞いです。