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

.NET 6.0与EF Core:如何对含LEFT JOIN的LINQ查询做单元测试?

问题原因

Moq默认模拟的DbSet没有正确处理LINQ GroupJoin(即LEFT JOIN里的into子句)后的空集合场景。当completeInformationList为null时,调用DefaultIfEmpty()会触发ArgumentNullException——因为该方法要求源集合不能为null。而INNER JOIN不需要调用DefaultIfEmpty(),因此不会触发这个问题。

解决方案

不要直接依赖Moq对IQueryable的默认模拟,改用实际的List<T>.AsQueryable()来构建模拟数据源,这样能正确处理分组后的空集合逻辑。

具体实现步骤

  1. 构造包含测试场景的实体列表:需要包含有匹配订单和无匹配订单的CostDetails记录,以及对应的OrderInformation记录。
  2. 基于这些列表模拟DbSet,让DbSet的IQueryable实现指向列表的查询提供者。
  3. 模拟DbContext,返回上述模拟的DbSet实例。

示例代码

// 1. 构造测试数据
var testCostDetails = new List<CostDetails>
{
    new CostDetails { FK_INVTABLEUID = 1, MaximumRetailPrice = 199.99m },
    new CostDetails { FK_INVTABLEUID = 2, MaximumRetailPrice = 299.99m }, // 无对应订单的记录
    new CostDetails { FK_INVTABLEUID = 3, MaximumRetailPrice = 399.99m }
};

var testOrderInfos = new List<OrderInformation>
{
    new OrderInformation { FK_INVTABLEUID = 1, OrderId = "ORD-001" },
    new OrderInformation { FK_INVTABLEUID = 3, OrderId = "ORD-003" }
};

// 2. 模拟CostDetails的DbSet
var mockCostDbSet = new Mock<DbSet<CostDetails>>();
mockCostDbSet.As<IQueryable<CostDetails>>()
    .Setup(m => m.Provider).Returns(testCostDetails.AsQueryable().Provider);
mockCostDbSet.As<IQueryable<CostDetails>>()
    .Setup(m => m.Expression).Returns(testCostDetails.AsQueryable().Expression);
mockCostDbSet.As<IQueryable<CostDetails>>()
    .Setup(m => m.ElementType).Returns(testCostDetails.AsQueryable().ElementType);
mockCostDbSet.As<IQueryable<CostDetails>>()
    .Setup(m => m.GetEnumerator()).Returns(testCostDetails.GetEnumerator());

// 3. 模拟OrderInformation的DbSet
var mockOrderDbSet = new Mock<DbSet<OrderInformation>>();
mockOrderDbSet.As<IQueryable<OrderInformation>>()
    .Setup(m => m.Provider).Returns(testOrderInfos.AsQueryable().Provider);
mockOrderDbSet.As<IQueryable<OrderInformation>>()
    .Setup(m => m.Expression).Returns(testOrderInfos.AsQueryable().Expression);
mockOrderDbSet.As<IQueryable<OrderInformation>>()
    .Setup(m => m.ElementType).Returns(testOrderInfos.AsQueryable().ElementType);
mockOrderDbSet.As<IQueryable<OrderInformation>>()
    .Setup(m => m.GetEnumerator()).Returns(testOrderInfos.GetEnumerator());

// 4. 模拟DbContext
var mockDbContext = new Mock<YourDbContextType>();
mockDbContext.Setup(c => c.CostDetails).Returns(mockCostDbSet.Object);
mockDbContext.Setup(c => c.OrderInformation).Returns(mockOrderDbSet.Object);

验证逻辑

用上述模拟的DbContext执行你的LEFT JOIN查询时,completeInformationList会正确返回空集合而非null,调用DefaultIfEmpty()后会生成null的ci对象,最终返回包含所有CostDetails记录的结果集(无匹配订单的记录中ci.OrderId为null)。

内容的提问来源于stack exchange,提问作者shriiihariii

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 05:40:29