Nick
Nick

Reputation: 5872

Grouping and only returning the most recent record in LINQ query

I have a table AppointmentStatusHistory in the following format:

AppointmentId   AppointmentStatusId   Date
========================================================
1               1                     2/1/2012 (2nd January)
1               2                     2/2/2012 (2nd February)

I currently run a query against this to return the 'most recent' status for the appointment within a given timeframe.

My LINQ Query

items = (from t1 in db.AppointmentStatusHistories
         from t2 in db.TaskAppointments
                      .Where(o => (o.ID == t1.AppointmentId))
         from t3 in db.Tasks
                      .Where(o => (o.Task_ID == t2.Parent_Task_ID))
         where t1.Timestamp >= first && t1.Timestamp <= last
            && t3.Creator == user
         group t1 by t1.AppointmentId into grp
         select new UserAppointmentTargetModel
         {
             AppointmentId = grp.Key,
             AppointmentStatusId = grp.OrderByDescending(g => g.Timestamp)
                                      .FirstOrDefault()
                                      .AppointmentStatusId
    }
);

Using the above returns AppointmentStatusId status of '1' when first=1/1/2012 and last=2/1/2012.

Requirements

I hoped somebody may be able to give me some advice on amending this to meet the following conditions:

Upvotes: 0

Views: 2146

Answers (1)

Amy B
Amy B

Reputation: 110111

You just need to move the last part of the filtering to after the grouping/winnerpicking.

db.AppointmentStatusHistories
.Where(ash => first <= ash.TimeStamp) //omit all records that are before the range
.Where(ash => ash.Appointment.TaskAppointments.Any(ta => ta.Task.Creator == user))
.GroupBy(ash => ash.AppointmentId)
.Select(g => g.OrderByDescending(ash => ash.TimeStamp).First()) //pick a winner
.Where(ash => ash.TimeStamp <= last) //omit winners that are after the range
.Select(ash => new UserAppointmentTargetModel()
{
  AppointmentId = ash.AppointmentId,
  AppoinementStatusId = ash.AppointmentStatus,
  Date = ash.Timestamp
}

(obligatory query comprehension syntax form of the above)

from ash in db.AppointmentStatusHistories
where first <= ash.TimeStamp
where ash.Appointment.TaskAppointments.Any(ta => ta.Task.Creator == user)
group ash by ash.AppointmentId into g
let winner = g.OrderByDescending(ash => ash.TimeStamp).First()
where winner.TimeStamp <= last
select new UserAppointmentTargetModel()
{
  AppointmentId = winner.AppointmentId,
  AppoinementStatusId = winner.AppointmentStatus,
  Date = winner.Timestamp
}

Side notes:

I used navigation properties to do the user filtering. If you can't get that to work, go back to the joins.

It's always safe to call First on a Group. Groups aren't ever empty. There's no need for FirstOrDefault in this case.

I reused the ash variable name in the method style query to communicate type since it is declared in two different places where the type is not stated. I changed that to winner in the comprehension style query to communicate intent better since it is declared in one place where the type may be verified by inspection.

Also, I never use >= with dates. It can only lead to sorrow.

Upvotes: 4

Related Questions