如何使用linq

时间:2015-04-22 10:30:02

标签: javascript c# linq c#-4.0 linq-to-sql

我有一个linq查询结果,如图所示。在最终查询(未显示)中,我按照LeaveType对Year进行分组。但是,我想计算多年来每种类型的leaveCarriedOver的运行总计。也就是说,2010年生病的LeaveCarriedOver变成了#34;开放" 2011年的病假余额加上2011年的病假余额。

enter image description here

我在显示的结果列表上做了另一个查询,如下所示:

            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中进行。提前致谢

4 个答案:

答案 0 :(得分:0)

简单的实现是先获取可能的总计列表,然后从每个类别的详细信息中获取总和。

获取YearLeaveType的明确列表是一个分组依据并在每个组中首先选择。我们返回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(YearLeaveType)加上总数,以便为{{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 IntSum<>的已过滤详细信息的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();