我在CI环境中的TFS上运行单元测试,但今天失败了,但有以下异常:
Error:
System.InvalidOperationException : Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.
Stacktrace:
at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
at System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value)
at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.GetDisplayName(Type type)
at Microsoft.EntityFrameworkCore.Metadata.Internal.Model.FindEntityType(Type type)
at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.EntityEqualityRewritingExpressionVisitor.RewriteEntityEquality(ExpressionType nodeType, Expression left, Expression right)
at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.EntityEqualityRewritingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.EntityEqualityRewritingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Remotion.Linq.Clauses.WhereClause.TransformExpressions(Func`2 transformation)
at Remotion.Linq.QueryModel.TransformExpressions(Func`2 transformation)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryOptimizer.Optimize(QueryCompilationContext queryCompilationContext, QueryModel queryModel)
at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.OptimizeQueryModel(QueryModel queryModel, Boolean asyncQuery)
at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.CreateQueryExecutor[TResult](QueryModel queryModel)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](Expression query, IQueryModelGenerator queryModelGenerator, IDatabase database, IDiagnosticsLogger`1 logger, Type contextType)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass13_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at System.Linq.Queryable.Any[TSource](IQueryable`1 source)
at ModelNamespace.SomeRepository.TestMethod()
at TestNamespace.SomeRepositoryTest.TestMethod_Test()
此后,我再次在相同的提交上启动了相同的构建,并且成功。是不是很奇怪?
要设置InMemoryDatabase,我们使用以下帮助程序类:
public class DbContextTestHelper
{
public static DbContextOptions<CustomContext> PrepareData(Action<CustomContext> createData)
{
var options = CreateDbContextOptions();
SaveData(options, createData);
return options;
}
private static void SaveData(DbContextOptions<CustomContext> options, Action<CustomContext> createData)
{
// Insert seed data into the database using one instance of the context
using (var context = new CustomContext(options))
{
createData.Invoke(context);
context.SaveChanges();
}
}
private static DbContextOptions<CustomContext> CreateDbContextOptions()
{
return new DbContextOptionsBuilder<CustomContext>()
.UseInMemoryDatabase(Guid.NewGuid()
.ToString()) //Database with same name gets reused, so let's isolate the tests from each other...
.Options;
}
}
所以现在我们建立了许多这样的测试:
public class SomeRepositoryTest
{
[Fact]
public async Task TestMethod_Test()
{
var options = DbContextTestHelper.PrepareData(context =>
{
// do some initialisation
});
// Use a clean instance of the context to run the test
using (var context = new CustomContext(options))
{
var testee = new SomeRepository(context);
await testee.TestMethod(); // InvalidOperationException from above
// Assert something
}
}
}
当然,在许多情况下,我们会创建DbContextOptions<CustomContext>
,并且由于xUnit
提供的内置功能,它们自然会并行运行(当然,不是在同一个类中,而是用于许多测试)类)。
据我所知,为何有时会导致异常的可能性很小:
你知道发生了什么吗?
根据要求,这里是TestMethod()
被调用的System.Linq.Queryable.Any[TSource](IQueryable'1 source)
。
public void TestMethod(SomeObject someObject, List<DateTime> dates, bool someOption)
{
var res = _dbContext.SomeSet.Where(o =>
o.SomeObject.Equals(someObject) && dates.Contains(o.DateTime) && o.SomeOption == someOption);
if (res.Any()) // at System.Linq.Queryable.Any[TSource](IQueryable`1 source)
{
_dbContext.SomeSet.RemoveRange(res);
}
}