我有一个linq查询结果,如图所示。在最终查询(未显示)中,我按照LeaveType对Year进行分组。但是,我想计算多年来每种类型的leaveCarriedOver的运行总计。也就是说,2010年生病的LeaveCarriedOver变成了#34;开放" 2011年的病假余额加上2011年的病假余额。
我在显示的结果列表上做了另一个查询,如下所示:
var leaveDetails1 = (from l in leaveDetails
select new
{
l.Year,
l.LeaveType,
l.LeaveTaken,
l.LeaveAllocation,
l.LeaveCarriedOver,
RunningTotal = leaveDetails.Where(x => x.LeaveType == l.LeaveType).Sum(x => x.LeaveCarriedOver)
});
其中leaveDetails是图像的结果。
生成的RunningTotal不会按预期累积。我怎样才能达到我的初步目标。对任何想法开放 - 我的最后一个选择是在前端的javascript中进行。提前致谢
答案 0 :(得分:0)
简单的实现是先获取可能的总计列表,然后从每个类别的详细信息中获取总和。
获取Year
和LeaveType
的明确列表是一个分组依据并在每个组中首先选择。我们返回List<Tuple<int, string>>
,其中Int
是年份,string
是LeaveType
var distinctList = leaveDetails1.GroupBy(data => new Tuple<int, string>(data.Year, data.LeaveType)).Select(data => data.FirstOrDefault()).ToList();
然后我们想要每个元素的总数,因此您希望选择该列表以返回ID(Year
和LeaveType
)加上总数,以便为{{1}添加额外值}。
Tuple<int, string, int>
阅读上面的一行,您可以看到我们要列出的不同总数,创建一个新对象,存储var totals = distinctList.Select(data => new Tuple<int, string, int>(data.Year, data.LeaveType, leaveDetails1.Where(detail => detail.Year == data.Year && detail.LeaveType == data.LeaveType).Sum(detail => detail.LeaveCarriedOver))).ToList();
和Year
以供参考,然后设置最后一个LeaveType
Int
和Sum<>
的已过滤详细信息的Year
。
答案 1 :(得分:0)
如果我完全理解你要做什么,那么我认为我不会完全依赖内置的LINQ运算符。我认为(强调思考)内置LINQ运算符的任何组合都将在O(n ^ 2)运行时解决这个问题。
如果我要在LINQ中实现它,那么我将为IEnumerable创建一个类似于反应式扩展中的Scan
函数的扩展方法(或找到已经实现它的库):< / p>
public static class EnumerableExtensions
{
public static IEnumerable<TAccumulate> Scan<TSource, TAccumulate>(
this IEnumerable<TSource> source,
TAccumulate seed,
Func<TAccumulate, TSource, TAccumulate> accumulator)
{
// Validation omitted for clarity.
foreach(TSource value in source)
{
seed = accumulator.Invoke(seed, value);
yield return seed;
}
}
}
然后这应该在O(n log n)附近(因为操作的顺序):
leaveDetails
.OrderBy(x => x.LeaveType)
.ThenBy(x => x.Year)
.Scan(new {
Year = 0,
LeaveType = "Seed",
LeaveTaken = 0,
LeaveAllocation = 0.0,
LeaveCarriedOver = 0.0,
RunningTotal = 0.0
},
(acc, x) => new {
x.Year,
x.LeaveType,
x.LeaveTaken,
x.LeaveAllocation,
x.LeaveCarriedOver,
RunningTotal = x.LeaveCarriedOver + (acc.LeaveType != x.LeaveType ? 0 : acc.RunningTotal)
});
您没有说,但我认为数据来自数据库;如果是这种情况,那么您可以将leaveDetails
返回已经排序并在此处跳过排序。这会让你下到O(n)。
如果你不想创建一个扩展方法(或者去找一个),那么这将实现同样的目的(只是以一种更丑陋的方式)。
var temp = new
{
Year = 0,
LeaveType = "Who Cares",
LeaveTaken = 3,
LeaveAllocation = 0.0,
LeaveCarriedOver = 0.0,
RunningTotal = 0.0
};
var runningTotals = (new[] { temp }).ToList();
runningTotals.RemoveAt(0);
foreach(var l in leaveDetails.OrderBy(x => x.LeaveType).ThenBy(x => x.Year))
{
var s = runningTotals.LastOrDefault();
runningTotals.Add(new
{
l.Year,
l.LeaveType,
l.LeaveTaken,
l.LeaveAllocation,
l.LeaveCarriedOver,
RunningTotal = l.LeaveCarriedOver + (s == null || s.LeaveType != l.LeaveType ? 0 : s.RunningTotal)
});
}
如果你可以预先排序leaveDetails
,那么这也应该是O(n log n)或O(n)。
答案 2 :(得分:0)
如果我理解你想要的问题
decimal RunningTotal = 0;
var results = leaveDetails
.GroupBy(r=>r.LeaveType)
.Select(r=> new
{
Dummy = RunningTotal = 0 ,
results = r.OrderBy(o=>o.Year)
.Select(l => new
{
l.Year,
l.LeaveType ,
l.LeaveAllocation,
l.LeaveCarriedOver,
RunningTotal = (RunningTotal = RunningTotal + l.LeaveCarriedOver )
})
})
.SelectMany(a=>a.results).ToList();
这基本上是使用Select<TSource, TResult>
重载来计算运行余额,但首先按LeaveType分组,这样我们可以为每个LeaveType重置RunningTotal,然后在末尾取消组合。
答案 3 :(得分:0)
您必须在此处使用Window Function Sum。 EF Core和EF的早期版本不支持该功能。因此,只需编写SQL并通过Dapper运行它
SELECT
l.Year,
l.LeaveType,
l.LeaveTaken,
l.LeaveAllocation,
l.LeaveCarriedOver,
SUM(l.LeaveCarriedOver) OVER (PARTITION BY l.Year, l.LeaveType) AS RunningTotal
FROM leaveDetails l
或者,如果您使用的是EF Core,请使用软件包linq2db.EntityFrameworkCore
var leaveDetails1 = from l in leaveDetails
select new
{
l.Year,
l.LeaveType,
l.LeaveTaken,
l.LeaveAllocation,
l.LeaveCarriedOver,
RunningTotal = Sql.Ext.Sum(l.LeaveCarriedOver).Over().PartitionBy(l.Year, l.LeaveType).ToValue()
};
// switch to alternative LINQ translator
leaveDetails1 = leaveDetails1.ToLinqToDB();