views:

134

answers:

4

I have a linq query that needs to pull a date column out of a row. The expression currently looks like this

myObject.OrderByDescending(s=> s.MyDate).Where(s => s.CRAStatus.Description == "CheckedOut").FirstOrDefault().MyDate)

The problem is that if there are no rows that are "CheckedOut", the query will return a null and attempting to get "MyDate" will throw an exception. We have some verbose solutions, like:

.ForMember(dest => dest.CheckOutDate, opt => opt.MapFrom(src => {
    var temp = src.CRAStatusChangeEvents.OrderByDescending(s=> s.MyDate).Where(s => s.CRAStatus.Description == "CheckedOut").FirstOrDefault();
    return temp == null ? temp.MyDate : null;
}));

But it would be nice to find something a little more concise. Any Ideas?

+1  A: 
var checkedOut = myObject.Where(s => s.CRAStatus.Description == "CheckedOut");
if (checkedOut.Count() > 0) {
   var result = checkedOut.Max(s=> s.MyDate).MyDate;
}
bruno conde
you can replace checkedOut.Count > 0 with !checkedOut.Empty() for better readability
roman m
I'm afraid you can't in this case. IEnumerable<typeof(myObject)>
bruno conde
+3  A: 

One option is to set the default if empty to an "empty" instance (think of string.Empty--its a known instance that represents an empty result):

var date = (myObject
    .OrderByDescending(s=> s.MyDate)
    .Where(s => s.CRAStatus.Description == "CheckedOut")
    .DefaultIfEmpty(MyObject.Empty)
    .FirstOrDefault()).MyDate;

Here's a snippet that shows how it works:

var strings = new string[]{"one", "two"};
var length = 
    (strings.Where(s=>s.Length > 5)
    .DefaultIfEmpty(string.Empty)
    .FirstOrDefault()).Length;

run that and length is 0. Remove the DefaultIfEmpty line and you get a NRE.

Will
+1 for DefaultIfEmpty() ... didn't know about that one
roman m
It was a happy, happy day when I found that method. Damn, my life is empty.
Will
puttin that one in my back pocket. that's cool.
jlembke
+1  A: 

How about an extension method?

static class MyObjectEnumerableExtensions
{
    public static TMember GetMemberOfFirstOrDefault<TMember>(this IEnumerable<MyObject> items, Func<MyObject, TMember> getMember)
    {
        MyObject first = items.FirstOrDefault();
        if (first != null)
        {
            return getMember(first);
        }
        else
        {
            return default(TMember);
        }
    }
}

Sample usage:

List<MyObject> objects = new List<MyObject>();
objects.Add(new MyObject { MyDate = DateTime.MinValue });

var filteredObjects = from s in objects where s.MyDate > DateTime.MinValue select s;

DateTime date = filteredObjects.GetMemberOfFirstOrDefault(s => s.MyDate);

Console.WriteLine(date);
bobbymcr
+4  A: 

Why not

myObject.OrderByDescending(s=> s.MyDate)
        .Where(s => s.CRAStatus.Description == "CheckedOut")
        .Select(s => s.MyDate as DateTime?)
        .FirstOrDefault();

or

myObject.Where(s => s.CRAStatus.Description == "CheckedOut")
        .Max(s => s.MyDate as DateTime?);
Ruben
now, that's nice and clean !!!
roman m
don't forget "as DateTime?", if you don't add that, you'll still get a NRE.
Ruben
That is very nice! Thanks Ruben!
jlembke
In my case anyway "as DateTime?) isn't even needed so it's even more terse. Great job.
jlembke