如何解决将 retryPolicy 与 SqlAzureExecutionStrategy 一起使用时,避免“SqlParameter 已被另一个 SqlParameterCollection 包含”异常
我已阅读有关此异常的各种问题/建议。但是,当我使用重试策略时,我应该怎么做才能避免它?连接可能不会最终关闭,因此无法重用参数?
public class ReliablesqlCommand
{
public List<ResultType> ExecuteReader<ResultType>() where ResultType : new()
{
var list = new List<ResultType>();
var retryPolicy = new DWsqlAzureExecutionStrategy(sqlMaxRetryCount,sqlMaxDelay);
retryPolicy.Execute(() =>
{
list = new List<ResultType>();
using (var sqlConnection = new sqlConnection(ConnectionString))
{
using (var sqlCommand = new sqlCommand(CommandText,sqlConnection))
{
sqlCommand.CommandTimeout = CommandTimeout;
sqlCommand.CommandType = CommandType;
sqlCommand.Parameters.AddRange(Parameters.ToArray());
sqlCommand.Connection = sqlConnection;
sqlConnection.open();
using (sqlDataReader dataReader = sqlCommand.ExecuteReader())
{
while (dataReader.Read())
{
if (typeof(ResultType).BaseType == typeof(System.ValueType))
{
var sqlValue = dataReader.GetValue(0);
if (sqlValue == dbnull.Value)
list.Add(default);
else
list.Add((ResultType)ChangeType(sqlValue,typeof(ResultType)));
}
else
{
//handle complex types (objects)
ResultType item = new ResultType();
Type itemType = item.GetType();
for (int columnNr = 0; columnNr < dataReader.FieldCount; columnNr++)
{
PropertyInfo prop = itemType.GetProperty(dataReader.GetName(columnNr));
if (prop == null) continue;
var value = dataReader.GetValue(columnNr);
if (value == null || value == dbnull.Value)
{
prop.SetValue(item,null);
}
else
{
prop.SetValue(item,value);
}
}
list.Add(item);
}
}
sqlConnection.Close();
}
sqlCommand.Parameters.Clear();
}
}
});
return list;
}
}
public List<sqlParameter> Parameters { get; } = new List<sqlParameter>();
解决方法
在查看您的代码后,我可以想象以下内容。 (请注意,我还没有测试过。)
您将一个函数传递给 retryPolicy.Execute()
,它似乎可以正确处理您的数据库操作,处理所有连接、命令、数据读取器等。
但是,我假设 retryPolicy
已经可以开始执行该函数的新运行,而前一次运行仍处于活动状态/正在运行(或至少尚未完全完成)。在这种情况下,ReliableSqlCommand.Parameters
中的参数将被添加到 SqlCommand
的新实例中,这显然是不允许的,因为这些参数在先前在后台运行的函数调用中仍然“活着”(即可能还在等待数据库超时异常)。
我没有看到一个简单的稳定/可靠的修复方法。
在函数内,您可以尝试制作 Parameter
对象的新副本/实例,并将这些副本分配给 SqlCommand
实例。但如果您有输出参数,则必须在之后更新 ReliableSqlCommand.Parameters
集合。当有多个运行/重叠的函数调用时,这也可能很棘手。
我认为您需要做的是确保从旧命令中删除参数,或者缓存命令
如果我理解正确,Execute
函数会重试 lambda,并在此过程中吞下任何异常。它不会同时执行多次。
不幸的是,SqlCommand.Dispose
没有从命令中删除参数。
所以选项 1 是:
using (var sqlCommand = new SqlCommand(CommandText,sqlConnection))
{
try
{
.......
}
finally
{
sqlCommand.Parameters.Clear();
}
}
在我看来,一个更好的选择,因为一个参数应该只用于一个命令,所以也缓存命令。
这个没有什么问题,只要每次都改变连接即可。
public ReliableSqlCommand
{
public SqlCommand Command { get; set; }
然后使用现有的 using (var sqlCommand = new SqlCommand...
代替 _command
:
_command.Connection = sqlConnection;
如果你不想直接暴露你的命令对象,你可以做一个添加和删除参数的包装器。
处理 SqlCommand
并不是绝对必要的,因为它的 Dispose
什么都不做。但为了一致性起见,您可能希望 ReliableSqlCommand
也是一次性的。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。