user1431743
user1431743

Reputation: 107

Sort list and select object based on order by

I have a List<MyClass> with Status and Date fields. I want to return a single MyClass where the Status = X.

The problem is there could be more than one in the list, in which case I want to sort on Date and return the latest one.

Can this be done in LINQ in a single expression?

Upvotes: 2

Views: 1447

Answers (3)

Harald Coppoolse
Harald Coppoolse

Reputation: 30492

So you a sequence of MyClass objects and an object X, and you want to find the newest MyClass object that has a value for Status equal to X

var result = myList.Where(myItem => myItem.Status == X)
                   .OrderByDescending(myItem => myItem.Date)
                   .FirstOrDefault();

Although this will work, the sorting is not very efficient: after you've find the first element, it sorts the 2nd, 3rd, etc, while you know it will not use these other elements, so why sort them? If you use Aggregate you will only have to enumerate once

var result = myList.Where(...)
    .Aggregate( (newestItem, nextItem) => (newestItem.Date < nextItem.Date) ? 
           newestItem : nextItem);

This puts the first element in newestItem and scans the rest of the list. If any nextItem has a new data, then put this next item in newestItem, otherwise do not change newestItem. At the end return newestItem, which is the one with the newest date that you want.

This works only if you are certain that there is at least one remaining item after the where. If it must also work with empty lists, consider creating an extension function:

static TResult GetNewestOrDefault<TSource, TResult>(this IEnumerable<TSource> source,
   Func<Tsource, DateTime> dateSelector)
{
    var enumerator = source.GetEnumerator();

    if (enumerator.MoveNext())
    {   // at least one element; initialize newest:
        TSource newestElement = enumerator.Current;
        DateTime newestDate = dateSelector(newest);

        // scan the rest of the sequence and check if newer:
        while (enumerator.MoveNext())
        {
            Tsource nextElement = enumerator.Current;
            Datetime nextDate = dateSelector(nextElement);

            // if next element has a newer date, remember it as newest:

            if (newestDate < nextDate
            {
                newestElement = nextElement;
                newestDate = nextDate,
            }
        }
        // scanned all elements exactly once
        return newestElement;
    }
    else
       // empty sequence, return default
       return default(TResult);
}

Usage:

MyClass result = myList.Where(myItem => myItem.Status == X)
                       .GetNewestOrDefault();

This will scan your sequence exactly once.

Upvotes: 0

Slava Utesinov
Slava Utesinov

Reputation: 13498

If list already located at memory try this:

var answer = list.Where(x => x.Status == X).OrderBy(x => x.Date).LastOrDefault();

At case of Entity Framework try another approach:

var answer = context.Table.Where(x => x.Status == X).OrderByDescending(x => x.Date).FirstOrDefault();

Upvotes: 1

Nomi Ali
Nomi Ali

Reputation: 2262

You can use lambda expression:

var yourResult = dbo.YourList.OrderByDescending(t=>t.Date).FirstOrDefault(t=>t.Status == 'X');

Upvotes: 3

Related Questions