微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

将 retryPolicy 与 SqlAzureExecutionStrategy 一起使用时,避免“SqlParameter 已被另一个 SqlParameterCollection 包含”异常

如何解决将 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;
            }
}

ReliablesqlCommand 包含此属性

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 举报,一经查实,本站将立刻删除。