简体   繁体   中英

How to reconcile first and last record in LINQ

I have a table in my SQL database that tracks an employee's in time and out time. A typical record would look like this

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 is just an auto increment column
Device is the device that they tapped their staff card on, to clock in/or out
DateTime is the time that they tapped their staff card

I would like to know, at the end of the day, when I'm generating a report, how can I reconcile their in-time with out-time, such that the output may look like this:

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

Caveats:
- Note that Employee 1 has 2 records for "InReader", I would want to take the earlier record
- Employee 2 has 2 records for "OutReader", I would want to take only his latest record

How do I reconcile the IN and OUT records using LINQ? (or TSQL if it's not possible in LINQ)

I made you this query, and tested in LinqPad. I'll give you the full code so you can try it yourself.

The query itself:

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
            })

The full code sample:

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; }
}
 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
                }

Something like that above should do the trick.

Use Min, Max aggregate to returns the smallest or largest element in the sequence and use GroupBy for ordering.

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()});
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 });

I suppose that you know your employee's ID (or may be you are going through a list) and the date at which you're generating your report, so first thing you need to do is to get your employee's in and out times of the day with sth. like this:

 //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;

You could also easily rewrite this query in order to obtain all employees times of the day, and filter by EmployeeId lately (it depends on what is better for your case).

And after that you can obtain easily from your employee's in and out times of the day the date you need for your report like this:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM