EF Core查询:获取已确认订单总量小于可用库存的产品
嘿,我来帮你搞定这个查询问题!你的需求是找出所有已确认订单的总数量小于对应产品可用库存的Product,之前的查询没生效大概率是因为没处理空集合的Sum结果问题,而且完全不需要用GroupBy,咱们直接调整LINQ写法就能让EF Core正确转换成SQL在数据库端执行,不用拉到内存里处理。
问题分析
你原来的查询:
_context.Products.Where(product => product.Orders.Where(order => order.Confirmed).Sum(order => order.Quantity) < product.AvailableQuantity)
这里的坑在于:当某个Product没有任何已确认订单时,Sum(order => order.Quantity)在SQL里会返回NULL,而NULL < product.AvailableQuantity的结果是false,导致这些符合条件(总数量为0)的Product被过滤掉了。另外,EF Core对导航属性的Sum处理需要注意可空类型的转换,才能正确生成SQL。
解决方案1:修复导航属性查询
我们只需要把Sum的结果转成可空int,再用??运算符处理NULL的情况,把NULL替换成0:
var validProducts = _context.Products .Where(product => product.Orders .Where(order => order.Confirmed) .Sum(order => (int?)order.Quantity) ?? 0 < product.AvailableQuantity) .ToList();
这样一来,没有已确认订单的Product会被正确计算为总数量0,只要可用库存大于0就会被选中,而且整个查询会完全在数据库端执行,不会加载多余数据到内存。
解决方案2:使用分组连接(更直观的SQL转换)
如果你更习惯用查询语法,也可以用左连接分组的方式,先计算每个Product的已确认订单总数量,再和Product表关联筛选:
var validProducts = from product in _context.Products join order in _context.Orders.Where(o => o.Confirmed) on product.Id equals order.ProductId into confirmedOrders let totalConfirmed = confirmedOrders.Sum(o => (int?)o.Quantity) ?? 0 where totalConfirmed < product.AvailableQuantity select product;
这种写法EF Core会转换成LEFT JOIN加GROUP BY的SQL,同样在数据库端完成计算,性能上和第一种方案差不多,看你个人习惯选择。
验证一下
这两种写法都会生成类似下面的SQL(以SQL Server为例),确保所有计算都在数据库里完成,完全不会用到AsEnumerable():
SELECT [p].[Id], [p].[AvailableQuantity] FROM [Products] AS [p] LEFT JOIN [Orders] AS [o] ON [p].[Id] = [o].[ProductId] AND [o].[Confirmed] = 1 GROUP BY [p].[Id], [p].[AvailableQuantity] HAVING COALESCE(SUM([o].[Quantity]), 0) < [p].[AvailableQuantity]
内容的提问来源于stack exchange,提问作者joa77

