C# メソッド内で、複数の行を返す次の SQL クエリを実行します。
SELECT [Data], [Version]
FROM [dbo].[Table]
WHERE [Id]=@uniqueId AND [ReferenceId] IS NULL
ORDER BY [Version] Asc
次に、結果を反復処理し、テーブルを更新するメソッドを呼び出します。
while (sqlDataReader.Read())
{
SqlBytes data = sqlDataReader.GetSqlBytes(0);
SqlInt64 version = sqlDataReader.GetSqlInt64(1);
UpdateReference(data, version);
}
UpdateReference(data, version)
{
// do database unrelated stuff with data
UPDATE [dbo].[Table]
SET [dbo].[Table].[ReferenceId]=..., [dbo].[Table].[Data]=...
WHERE [dbo].[Table].[Id]=@uniqueId AND [dbo].[Table].[Version]=@version
}
しばらくの間、これはうまくいきましたが、突然(SELECT ... INNER JOIN
同じテーブルでいくつかのクエリを実行した後)停止しました。最初の SELECT に対してトランザクション スコープを作成します (同じメソッドで を呼び出しますUpdateReference()
)。
using (TransactionScope scope = new TransactionScope())
SELECT ...
while (sqlDataReader.Read()) ... UpdateReference();
次の例外が発生します。
トランザクションは中止されました。
トランザクション スコープを削除すると、UPDATE の呼び出し中にしばらくするとタイムアウト例外が発生します。
タイムアウトになりました。操作が完了する前にタイムアウト期間が経過したか、サーバーが応答していません。
しかし、これは SQL Server の問題ではないようです。また、奇妙なことに、一部のレコードでは、そのような問題は発生しません。問題が発生するのは、特定のテーブル レコードに対して最初の SELECT が使用された場合のみです。
これまでにわかったことは次のとおりです。
- (コードから) クエリを個別に実行すると、すべて正常に動作します。
- SQL Management Studioで個別に実行すると、両方のクエリが期待どおりに機能します
SELECT
(今のところ?) うまくいくように見える 1 つの解決策は、最初のクエリの結果をリストに保存し、終了後にリスト要素の更新を呼び出すことです。
List<long> versionList = new List<long>();
List<byte[]> dataList = new List<byte[]>();
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// Execute SELECT ...
using (SqlCommand sqlCommand = new SqlCommand(selectStatement, connection))
{
...
using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
SqlBytes data = sqlDataReader.GetSqlBytes(0);
SqlInt64 version = sqlDataReader.GetSqlInt64(1);
// Store result to lists
versionList.Add(version.Value);
dataList.Add((byte[])data.ToSqlBinary(););
}
}
}
}
// Everything works as expected if this loop is placed here; but if it is placed within the above SqlConnection using clause, an exception is thrown:
// "Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configurationfor MSDTC using the Component Services Administrative tool."
for (int i = 0; i < versionList.Count; i++)
{
UpdateReference(dataList[i], versionList[i]);
}
scope.Complete();
}
この解決策が有効かどうか (最適よりも多くのメモリを使用する以外に)、またはその他の潜在的な問題が発生する可能性があるかどうかはわかりません。ここで何が起こっているのか、そしてそれを解決する最善の方法についての洞察に感謝します。
更新 1
わかりやすくするために、これが問題を修正した方法です。
TransactionScope の外で SELECT を実行し、結果をリストに保存します。
これらのリストを反復し、そのコンテンツを UPDATE にフィードします。これは TransactionScope に含まれています。
このソリューションを自由に批判/改善してください。
Method1()
{
List<long> versionList = new List<long>();
List<byte[]> dataList = new List<byte[]>();
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// Execute SELECT ...
using (SqlCommand sqlCommand = new SqlCommand(selectStatement, connection))
{
...
using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader())
{
while (sqlDataReader.Read())
{
SqlBytes data = sqlDataReader.GetSqlBytes(0);
SqlInt64 version = sqlDataReader.GetSqlInt64(1);
// Store result to lists
versionList.Add(version.Value);
data.Add((byte[])data.ToSqlBinary());
}
}
}
// Call update
for (int i = 0; i < versionList.Count; i++)
{
UpdateReference(dataList[i], versionList[i]);
}
}
}
UpdateReference(data, version)
{
...
using (TransactionScope scope = new TransactionScope())
{
using (SqlConnection connection = new SqlConnection(this.ConnectionString))
{
connection.Open();
UPDATE [dbo].[Table]
SET [dbo].[Table].[ReferenceId]=..., [dbo].[Table].[Data]=...
WHERE [dbo].[Table].[Id]=... AND [dbo].[Table].[Version]=@version
}
scope.Complete();
}
}