You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:12:34