如何在 EF6 的 LINQ 查询中使用 Sum 聚合函数?
支付小周
结论
计算某个用户全部付款记录的总额时,不需要使用 group by。筛选出该用户的记录后,直接调用 Sum 即可:
decimal totalPayments = portalEntities.tblPortalPayments
.Where(p => p.userId == _userID)
.Sum(p => p.paymentsToDate);
对应的 SQL 查询是:
SELECT SUM(paymentsToDate)
FROM tblPortalPayments
WHERE userId = @userId
为什么不能使用 c.Sum
原查询中的 c 是一条 tblPortalPayment 实体记录,只能访问这条记录的字段或属性,例如 paymentDate 和 paymentsToDate。
Sum 不是实体的属性,而是用于集合或分组的 LINQ 聚合方法。因此,不能对单条记录调用 c.Sum(...),应该对 Where 返回的记录集合调用:
portalEntities.tblPortalPayments
.Where(p => p.userId == _userID)
.Sum(p => p.paymentsToDate);
EF6 会将这段 LINQ 转换成数据库端的 SUM 查询,而不是先把所有付款记录加载到内存中再计算。
处理没有匹配记录的情况
没有匹配记录时,SQL 的 SUM 可能返回 NULL。为了避免查询结果为空时发生异常,可以先把参与计算的字段转换为可空类型,再设置默认值:
decimal totalPayments = portalEntities.tblPortalPayments
.Where(p => p.userId == _userID)
.Sum(p => (decimal?)p.paymentsToDate) ?? 0m;
如果 paymentsToDate 的实际类型不是 decimal,需要改用对应的可空类型。例如,该字段为 double 时:
double totalPayments = portalEntities.tblPortalPayments
.Where(p => p.userId == _userID)
.Sum(p => (double?)p.paymentsToDate) ?? 0d;
也可以写成查询表达式:
decimal totalPayments =
(from p in portalEntities.tblPortalPayments
where p.userId == _userID
select (decimal?)p.paymentsToDate)
.Sum() ?? 0m;
按日期分别求和时才需要分组
如果要计算每个付款日期的合计金额,才需要使用 group by:
var paymentsByDate =
from p in portalEntities.tblPortalPayments
where p.userId == _userID
group p by p.paymentDate into paymentGroup
select new
{
PaymentDate = paymentGroup.Key,
Sum = paymentGroup.Sum(p => p.paymentsToDate)
};
这里的 paymentGroup 是一组记录,因此可以调用 paymentGroup.Sum(...)。查询会返回多条结果,每个日期对应一个合计金额,这与计算全部记录的总额不同。
关于 portalEntities 属性
这个属性不影响 Sum 的用法,但当前实现有两个问题:
catch块吞掉了异常。一旦数据库上下文创建失败,就无法得知真正的原因。DbContext持有数据库连接等资源,需要明确管理其生命周期并及时释放。
如果操作范围较小,可以使用 using:
decimal totalPayments;
using (var context = new portalEntities())
{
totalPayments = context.tblPortalPayments
.Where(p => p.userId == _userID)
.Sum(p => (decimal?)p.paymentsToDate) ?? 0m;
}
如果上下文由依赖注入或应用框架统一管理,应遵循框架规定的生命周期。无论采用哪种方式,都不应静默忽略创建 portalEntities 时发生的异常。
备注:内容仅供参考。
