【问题标题】:How to reconcile first and last record in LINQ如何在 LINQ 中协调第一条记录和最后一条记录
【发布时间】:2013-06-21 07:39:05
【问题描述】:

我的 SQL 数据库中有一个表,用于跟踪员工的上班时间和下班时间。典型的记录如下所示

Id           Device               DateTime                    EmployeeId    
-------------------------------------------------------------------------
1            InReader             2013/05/05 08:00:00         1
2            InReader             2013/05/05 08:00:05         1
3            InReader             2013/05/05 08:01:00         2
4            InReader             2013/05/05 08:02:00         3
5            InReader             2013/05/05 08:03:00         4
6            OutReader            2013/05/05 17:00:00         1
7            OutReader            2013/05/05 17:05:05         2
8            OutReader            2013/05/05 17:05:10         2
9            OutReader            2013/05/05 17:10:00         3
10           OutReader            2013/05/05 17:30:00         4

Id 只是一个自增列
设备是他们用来刷员工卡、打卡/打卡的设备
DateTime 是他们敲员工卡的时间

我想知道,在一天结束时,当我生成报告时,我怎样才能协调他们的及时与超时,使得输出可能如下所示:

Employee Id            In time                    Out time
-----------------------------------------------------------------------
1                      2013/05/05 08:00:00        2013/05/05 17:00:00
2                      2013/05/05 08:01:00        2013/05/05 17:05:10
3                      2013/05/05 08:02:00        2013/05/05 17:10:00
4                      2013/05/05 08:03:00        2013/05/05 17:30:00

注意事项:
- 请注意,员工 1 有 2 条“InReader”记录,我想取较早的记录
- 员工 2 有 2 条“OutReader”记录,我只想获取他的最新记录

如何使用 LINQ 协调 IN 和 OUT 记录? (或 TSQL,如果在 LINQ 中不可能)

【问题讨论】:

  • 记录是否只有一天?他们每晚都擦吗?

标签: c# .net sql linq logic


【解决方案1】:

我向您提出了这个查询,并在 LinqPad 中进行了测试。我会给你完整的代码,你可以自己试试。

查询本身:

tracks.GroupBy(x => x.EmployeeId)
      .Select(x => new 
            {
                EmployeeId = x.Key,
                InTime = x.FirstOrDefault(y => y.Device.Equals("InReader")).DateTime,
                OutTime = x.LastOrDefault(y => y.Device.Equals("OutReader")).DateTime
            })

完整的代码示例:

void Main()
{
    var tracks = new[]
    {
        new Track{Id = 1, Device = "InReader", DateTime = new DateTime(2013,5,5,8,0,0), EmployeeId = 1},
        new Track{Id = 2, Device = "InReader", DateTime = new DateTime(2013,5,5,8,0,5), EmployeeId = 1},
        new Track{Id = 3, Device = "InReader", DateTime = new DateTime(2013,5,5,8,1,0), EmployeeId = 2},
        new Track{Id = 4, Device = "InReader", DateTime = new DateTime(2013,5,5,8,2,0), EmployeeId = 3},
        new Track{Id = 5, Device = "InReader", DateTime = new DateTime(2013,5,5,8,3,0), EmployeeId = 4},

        new Track{Id = 6, Device = "OutReader", DateTime = new DateTime(2013,5,5,17,0,0), EmployeeId = 1},
        new Track{Id = 7, Device = "OutReader", DateTime = new DateTime(2013,5,5,17,5,5), EmployeeId = 2},
        new Track{Id = 8, Device = "OutReader", DateTime = new DateTime(2013,5,5,17,5,10), EmployeeId = 2},
        new Track{Id = 9, Device = "OutReader", DateTime = new DateTime(2013,5,5,17,10,0), EmployeeId = 3},
        new Track{Id = 10, Device = "OutReader", DateTime = new DateTime(2013,5,5,17,30,0), EmployeeId = 4},
    };

        // the Query
        tracks
    .GroupBy(x => x.EmployeeId)
    .Select(x => new 
        {
            EmployeeId = x.Key,
            InTime = x.FirstOrDefault(y => y.Device.Equals("InReader")).DateTime,
            OutTime = x.LastOrDefault(y => y.Device.Equals("OutReader")).DateTime
        })
}

public class Track
{
    public int Id { get; set; }

    public string Device { get; set; }

    public DateTime DateTime { get; set; }

    public int EmployeeId { get; set; }
}

【讨论】:

  • OP 需要每天进/出时间(这将给出每个员工所有时间的第一条和最后一条记录,并且还假设记录按日期时间排序)。
  • 啊,是的。这将需要在 DateTime 中对当天进行一些分组
【解决方案2】:

使用Min, Max 聚合返回序列中的最小或最大元素,并使用GroupBy 进行排序。

var result=YourTableRowCollection.GroupBy(x=>x.EmployeeId)
                            .Select(x=>new { EmployeeId=x.Key,
                                             InTime=x.Min(t=>DateTime.Parse(t.InTime)).ToString(),
                                             OutTime=x.Max(t=>DateTime.Parse(t.OutTime)).ToString()});

【讨论】:

  • 但这是否处理同一员工的重复进入或退出记录?
  • @PapyrusBit,代码已修改,如有任何进一步的信息请告诉我
  • 那么当为第二天添加条目时会发生什么?另外,如果一个人在一天打卡并且工作超过午夜怎么办?
  • 也许最好将设备包含在您的算法中......以防有人忘记打卡但打卡等
  • @PaulZahra,这是您必须实施的事情,因为您是了解所有业务案例的最佳人选,例如 (a) 如果员工在不刷卡的情况下潜入后门怎么办或 (b) 如果员工继续在办公室(正如 Christian Dietz 建议的那样)再呆一天。
【解决方案3】:
 var x = from current in context.Employess
                let firstRecord = current.Where(c=> c.Track.Device == "InReader").OrderBy(c => c.DateTime).First()
                let lastRecord = current.Where(c=> c.Track.Device == "OutReader").OrderBy(c => c.DateTime).Last()
                select{
                    // do something
                }

上面的方法应该可以解决问题。

【讨论】:

  • 此代码可能会抛出错误,因为您使用的是First()Last()。我推荐使用FirstOrDefault()LastOrDefault() 方法
  • 因此短语“类似的东西”,它是一个方向的推动,而不是一个完整的解决方案
【解决方案4】:
var result = table.Where(e => e.Device == "InReader")
                    .GroupBy(e => e.EmployeeId)
                        .Select(g => g.OrderBy(d => d.DateTime).First())
            .Join(
            table.Where(e => e.Device == "OutReader")
                    .GroupBy(e => e.EmployeeId)
                        .Select(g => g.OrderByDescending(d => d.DateTime).First()), 
            t => t.EmployeeId, t => t.EmployeeId,(t, i) => new { Id = t.EmployeeId, In = t.DateTime, Out = i.DateTime });

【讨论】:

    【解决方案5】:

    我想您知道您的员工的 ID(或者您可能正在查看一个列表)以及您生成报告的日期,所以您需要做的第一件事就是了解您的员工的进出时间与某事的一天。像这样:

     //first second of the day
     DateTime firstSecondOfTheDay = dateToCheck.Subtract(dateToCheck.TimeOfDay);
     //last second of the day 
     TimeSpan endOfDay = new TimeSpan(23, 59, 59);
     DateTime lastSecondOfTheDay = firstSecondOfTheDay.Add(endOfDay);
     var employeeDayInOut = from emp in context.Employess
                            where (emp.DateTime >= firstSecondOfTheDay) &
                                  (emp.DateTime <= lastSecondOfTheDay) &
                                  (emp.EmployeeId == idToCheck)
                            select emp;
    

    您还可以轻松地重写此查询以获取一天中的所有员工时间,并最近按 EmployeeId 过滤(这取决于您的情况更好)。

    之后,您可以轻松地从员工一天中的上班和下班时间获取您需要的报告日期,如下所示:

      employeeDayInOut.Max(emp => emp.DateTime);
      employeeDayInOut.Min(emp => emp.DateTime);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-12-28
      • 2017-05-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-27
      相关资源
      最近更新 更多