c#linq获取当前值和上一个值

时间:2016-02-29 22:53:36

标签: c# linq

在下面的linq查询中,我想添加一个获取前一个Department以及当前Department的属性。 下面的linq查询返回:

EffectiveDate EmployeeID  Department
11/4/2012     10          0000 
1/14/2013     10          9121 
2/2/2016      10          9123 

如何显示当前Department旁边的上一个Department

EffectiveDate EmployeeID  Department PreviousDepartment
11/4/2012     10          0000       null
1/14/2013     10          9121       0000
2/2/2016      10          9123       9121

这是当前查询

var users = from s in userTable
            where s.EmployeeID == "10"
            group new {s} by new { s.EmployeeID, s.Department} into g
            select new 
            {
                EffectiveDate = g.Max(m => m.s.EffectiveDate), 
                EmployeeID = g.Key.EmployeeID, 
                Department = g.Key.Department
                //PreviousDepartment = ???
            };

感谢您的帮助

5 个答案:

答案 0 :(得分:1)

您可以使用LINQ中的Zip方法生成当前和上一对。一个缺点是,它确实枚举了两次输入(如果它来自数据库,可能会出现问题),但另一方面,它并不能将整个输入保存在内存中,而是它的输入懒惰(即你不必全部消费)。

例如,使用int值作为演示:

[TestMethod]
public void CurrentAndPrevious()
{
    var input = new int[] { 1, 2, 3, 4, 5 };

    var output = Enumerable.Repeat(0,1)   // an initial zero value
         .Concat(input)                   // followed by the list
                                          // zipped with the list
         .Zip(input, (x, y) => new {current = y, previous = x});

    // a test that passes (using FluentAssertions syntax)
    string.Join(",", output.Select(x => $"({x.current},{x.previous})"))
       .Should().Be("(1,0),(2,1),(3,2),(4,3),(5,4)");
}

答案 1 :(得分:0)

一种方法是将数据存储在内存中,然后像这样修改它:

N2

请注意,代码正在生成var users = from s in userTable where s.EmployeeID == "10" group new {s} by new { s.EmployeeID, s.Department} into g select new MyClass { EffectiveDate = g.Max(m => m.s.EffectiveDate), EmployeeID = g.Key.EmployeeID, Department = g.Key.Department PreviousDepartment = null }; var result = users.ToList(); for(int i = 1; i < result.Count; i++) { result[i].PreviousDepartment = result[i-1].Department; } 的新实例,而不是匿名类型,因为匿名类型属性是只读的。确保使用正确的属性创建此类。

答案 2 :(得分:0)

如果您使用Interactive Extensions(NuGet包Ix-Main),您可以执行以下操作:

var users = 
    userTable
    .Where(x => x.EmployeeID == "100")
    .GroupBy(x => new { x.EmployeeID, x.Department })
    .Select(x => new
        {
            EffectiveDate = x.Max(m => m.EffectiveDate),
            EmployeeID = x.Key.EmployeeID,
            Department = x.Key.Department
        })
    .OrderBy(x => x.EffectiveDate)
    .AsEnumerable()
    .Scan(
        new
        {
            EffectiveDate = default(DateTime),
            EmployeeID = default(string),
            Department = default(string),
            PreviousDepartment = default(string)
        },
        (a, x) => new
        {
            EffectiveDate = x.EffectiveDate,
            EmployeeID = x.EmployeeID,
            Department = x.Department,
            PreviousDepartment = a.Department
        });

好处是你坚持使用声明性编程风格。缺点是它比对内存中的数据进行操作更冗长,更难阅读。哦,您可能需要安装NuGet包。对我来说这不是一个问题,因为我不认为我在没有添加Ix-Main的情况下开始任何项目。

Ix-Main,没有它就永远不会离开家。

答案 3 :(得分:0)

实现它的方法语法。

var result = userTable.Select((x, i) => { Department j = null ; if (i>0) j = userTable.ElementAt(i-1).Department;  return new { x.EffectiveDate , x.EmployeeID  , x.Department , j};});

答案 4 :(得分:0)

大多数其他答案都强制 IEnumerable 被完全迭代,在某些情况下是两次。 无需任何额外迭代就可以做到这一点,换句话说:与LINQ 迭代流程。

public static IEnumerable<(T current, T previous)> WithLag<T>(this IEnumerable<T> source)
{
    var previous = default(T);
    var hasPrev = false;
    foreach (var item in source)
    {
        if(hasPrevious)
            yield return (item, previous);
        else
            yield return (item, default(T));

        hasPrev = true;
        previous = item;
    }
}

然后您可以以正常的 LINQ 方式使用它(您需要方法语法):

var users = userTable
            .Where(s => s.EmployeeID == "10")
            .GroupBy(s => new { s.EmployeeID, s.Department})
            .WithLag()
            .Select(gLag => new 
            {
                EffectiveDate = gLag.current.Max(m => m.s.EffectiveDate), 
                EmployeeID = gLag.current.Key.EmployeeID, 
                Department = gLag.current.Key.Department
                PreviousDepartment = gLag.previous.Key.Department
            });