在 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
完成后对列表元素调用更新:
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,将结果存储到列表中;
迭代这些列表并将其内容提供给包含在 TransactionScope 中的 UPDATE
随意批评/改进此解决方案:
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();
}
}