EF6 重试过程为 SqlQuery 命令抛出 "The SqlParameter is already contained by another SqlParameterCollection"
EF6 Retrying procedure throws "The SqlParameter is already contained by another SqlParameterCollection" for SqlQuery command
我正在尝试使用 DBExecutionStrategy 重试已超时的查询,但是当超时发生时我收到错误 "The SqlParameter is already contained by another SqlParameterCollection"。我正在使用 EF6。
我的查询:
using (var ctx = new EntityModel())
{
IEnumerable<ItemResponse> items= ctx.Database.SqlQuery<ItemResponse>(
"spItemListGet @UserID", new SqlParameter("@UserID", UserID)
).ToList();
}
我的执行策略:
protected override bool ShouldRetryOn(Exception ex)
{
bool retry = false;
SqlException sqlException = ex as SqlException;
if (sqlException != null)
{
int[] errorsToRetry =
{
-2, //Timeout
};
if (sqlException.Errors.Cast<SqlError>().Any(x => errorsToRetry.Contains(x.Number)))
{
retry = true;
}
else
{
throw ex; //dont retry
}
}
return retry;
}
堆栈跟踪:
System.ArgumentException: The SqlParameter is already contained by another SqlParameterCollection.
at System.Data.SqlClient.SqlParameterCollection.Validate(Int32 index, Object value)
at System.Data.SqlClient.SqlParameterCollection.AddRange(Array values)
at System.Data.Entity.Core.Objects.ObjectContext.CreateStoreCommand(String commandText, Object[] parameters)
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteStoreQueryInternal[TElement](String commandText, String entitySetName, ExecutionOptions executionOptions, Object[] parameters)
at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass65`1.<ExecuteStoreQueryReliably>b__64()
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass65`1.<ExecuteStoreQueryReliably>b__63()
at System.Data.Entity.Infrastructure.DbExecutionStrategy.Execute[TResult](Func`1 operation)
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteStoreQueryReliably[TElement](String commandText, String entitySetName, ExecutionOptions executionOptions, Object[] parameters)
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteStoreQuery[TElement](String commandText, ExecutionOptions executionOptions, Object[] parameters)
at System.Data.Entity.Internal.InternalContext.<>c__DisplayClass14`1.<ExecuteSqlQuery>b__13()
at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
如何防止这个错误? Database.SqlQuery可以和执行策略一起使用吗?
简短的回答:不,你不能这样做(如果你的命令有参数)。
长答案:
这是该问题的最小重现。我从图片中剥离了执行策略,并用循环来伪造它。此逻辑在 ObjectContext 中实现,具体在 ExecuteStoreQueryInternalAsync
方法中。问题似乎是清理部分缺少 command.Parameters.Clear()
调用。
static void Main(string[] args)
{
TestQuery();
}
private static void TestQuery()
{
using (var ctx = new ProductContext())
{
var parameter = new SqlParameter("@ID", 1);
var commandText = "select * from product where ProductId = @ID";
Action a = () =>
{
IDbCommand command = new SqlCommand();
command.CommandText = commandText;
command.Parameters.Add(parameter);
command.Connection = ctx.Database.Connection;
if (command.Connection.State != ConnectionState.Open)
{
command.Connection.Open();
}
var reader = command.ExecuteReader();
try
{
throw new Exception();
while (reader.Read())
{
var pId = reader["ProductID"];
}
reader.Close();
}
catch (Exception exc)
{
//for simplification, we just swallow this error, but in reality the connection error
//would reach the IDbExecutionStrategy, and would do a retry. Instead we fake the retry
//with a loop below
}
finally
{
reader.Dispose();
//command.Parameters.Clear(); <--------- THIS LINE IS MISSING FROM EF
command.Dispose();
}
};
for (int i = 0; i < 2; i++) // we fake the retry with a loop now
{
a();
}
}
}
我正在尝试使用 DBExecutionStrategy 重试已超时的查询,但是当超时发生时我收到错误 "The SqlParameter is already contained by another SqlParameterCollection"。我正在使用 EF6。
我的查询:
using (var ctx = new EntityModel())
{
IEnumerable<ItemResponse> items= ctx.Database.SqlQuery<ItemResponse>(
"spItemListGet @UserID", new SqlParameter("@UserID", UserID)
).ToList();
}
我的执行策略:
protected override bool ShouldRetryOn(Exception ex)
{
bool retry = false;
SqlException sqlException = ex as SqlException;
if (sqlException != null)
{
int[] errorsToRetry =
{
-2, //Timeout
};
if (sqlException.Errors.Cast<SqlError>().Any(x => errorsToRetry.Contains(x.Number)))
{
retry = true;
}
else
{
throw ex; //dont retry
}
}
return retry;
}
堆栈跟踪:
System.ArgumentException: The SqlParameter is already contained by another SqlParameterCollection.
at System.Data.SqlClient.SqlParameterCollection.Validate(Int32 index, Object value)
at System.Data.SqlClient.SqlParameterCollection.AddRange(Array values)
at System.Data.Entity.Core.Objects.ObjectContext.CreateStoreCommand(String commandText, Object[] parameters)
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteStoreQueryInternal[TElement](String commandText, String entitySetName, ExecutionOptions executionOptions, Object[] parameters)
at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass65`1.<ExecuteStoreQueryReliably>b__64()
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass65`1.<ExecuteStoreQueryReliably>b__63()
at System.Data.Entity.Infrastructure.DbExecutionStrategy.Execute[TResult](Func`1 operation)
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteStoreQueryReliably[TElement](String commandText, String entitySetName, ExecutionOptions executionOptions, Object[] parameters)
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteStoreQuery[TElement](String commandText, ExecutionOptions executionOptions, Object[] parameters)
at System.Data.Entity.Internal.InternalContext.<>c__DisplayClass14`1.<ExecuteSqlQuery>b__13()
at System.Data.Entity.Internal.LazyEnumerator`1.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
如何防止这个错误? Database.SqlQuery可以和执行策略一起使用吗?
简短的回答:不,你不能这样做(如果你的命令有参数)。
长答案:
这是该问题的最小重现。我从图片中剥离了执行策略,并用循环来伪造它。此逻辑在 ObjectContext 中实现,具体在 ExecuteStoreQueryInternalAsync
方法中。问题似乎是清理部分缺少 command.Parameters.Clear()
调用。
static void Main(string[] args)
{
TestQuery();
}
private static void TestQuery()
{
using (var ctx = new ProductContext())
{
var parameter = new SqlParameter("@ID", 1);
var commandText = "select * from product where ProductId = @ID";
Action a = () =>
{
IDbCommand command = new SqlCommand();
command.CommandText = commandText;
command.Parameters.Add(parameter);
command.Connection = ctx.Database.Connection;
if (command.Connection.State != ConnectionState.Open)
{
command.Connection.Open();
}
var reader = command.ExecuteReader();
try
{
throw new Exception();
while (reader.Read())
{
var pId = reader["ProductID"];
}
reader.Close();
}
catch (Exception exc)
{
//for simplification, we just swallow this error, but in reality the connection error
//would reach the IDbExecutionStrategy, and would do a retry. Instead we fake the retry
//with a loop below
}
finally
{
reader.Dispose();
//command.Parameters.Clear(); <--------- THIS LINE IS MISSING FROM EF
command.Dispose();
}
};
for (int i = 0; i < 2; i++) // we fake the retry with a loop now
{
a();
}
}
}