C# Moq模拟DbSet:新增对象无法通过FirstOrDefault查询问题
解决DbSet Mock中FirstOrDefault无法正确查询新增对象的问题
你的问题核心在于直接通过as将Expression<Func<T, bool>>转换为Func<T, bool>——这两种类型属于不同的类型体系,as转换会返回null,导致调用sourceList.FirstOrDefault(null)抛出异常,同时无法正确将查询条件应用到内存中的sourceList(包括新增的对象)。
修复方案
需要将传入的表达式编译为可执行的委托,再传入sourceList.FirstOrDefault。修改FirstOrDefault的Setup代码即可:
mock.Setup(x => x.FirstOrDefault(It.IsAny<Expression<Func<T, bool>>>())) .Returns((Expression<Func<T, bool>> predicate) => sourceList.FirstOrDefault(predicate.Compile()));
完整修改后的扩展方法
public static DbSet<T> BuildMockDbSet<T>(this IQueryable<T> source) where T : class { var mock = new Mock<DbSet<T>>(); var sourceList = source.ToList(); mock.As<IDbAsyncEnumerable<T>>() .Setup(x => x.GetAsyncEnumerator()) .Returns(new TestDbAsyncEnumerator<T>(sourceList.GetEnumerator())); mock.As<IQueryable<T>>() .Setup(x => x.Provider) .Returns(new TestDbAsyncQueryProvider<T>(source.Provider)); mock.As<IQueryable<T>>() .Setup(x => x.Expression) .Returns(source.Expression); mock.As<IQueryable<T>>() .Setup(x => x.ElementType) .Returns(source.ElementType); mock.As<IQueryable<T>>() .Setup(x => x.GetEnumerator()) .Returns(() => sourceList.GetEnumerator()); // 每次调用返回新枚举器,避免多次枚举耗尽的问题 mock.Setup(s => s.AsNoTracking()).Returns(mock.Object); mock.Setup(x => x.Include(It.IsAny<string>())).Returns(mock.Object); // 修复FirstOrDefault的查询逻辑 mock.Setup(x => x.FirstOrDefault(It.IsAny<Expression<Func<T, bool>>>())) .Returns((Expression<Func<T, bool>> predicate) => sourceList.FirstOrDefault(predicate.Compile())); mock.Setup(d => d.Add(It.IsAny<T>())) .Callback<T>((s) => sourceList.Add(s)) .Returns((T v) => v); return mock.Object; }
额外优化说明
原代码中GetEnumerator的Setup直接返回sourceList.GetEnumerator(),会导致多次枚举时,第一次枚举结束后枚举器已处于末尾,后续调用无结果。改成Returns(() => sourceList.GetEnumerator())后,每次调用都会生成新的枚举器,避免这个问题。
内容的提问来源于stack exchange,提问作者Wouter Vandenputte
相关产品推荐
相关产品推荐

