user449689
user449689

Reputation: 3154

Transforming if in single LINQ expression

I'm struggling to transform this piece of code in a single LINQ expression.

var x = values
    .Where(v => v.Columns.Any(c => c.Code == code && c.Value == value))
    .Select(v => v.Value)
    .FirstOrDefault();

if (x == null)
{
    x = values
        .Where(v => v.Columns.All(c => string.IsNullOrEmpty(c.Code))
        .Select(v => v.Value)
        .FirstOrDefault();

}

Basically I have a list of objects. Each objects contains a list of Column objects and a Value.

I want to filter the object that contains a specific Column object (based on Code and Value), but if this combination does not exist, I want to fall back to the entity that contains a list of Column objects all having Code equals to string.Empty (a wild card).

I have tried different approches like the following but without success:

var x = values
    .Where(v => v.Columns.Any(c => c.Code == code && c.Value == value)
        ? v.Columns.Any(c => c.Code == code && c.Value == value)
        : v => v.Columns.All(c => string.IsNullOrEmpty(c.Code))
    .Select(v => v.Value)
    .FirstOrDefault();

Upvotes: 1

Views: 97

Answers (2)

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186803

I suggest Concat both alternatives:

var x = values
    .Where(v => v.Columns.Any(c => c.Code == code && c.Value == value))
    .Select(v => v.Value)
    .Concat(values // second alternative if 1st returns empty cursor
       .Where(v => v.Columns.All(c => string.IsNullOrEmpty(c.Code))
       .Select(v => v.Value))
    .FirstOrDefault();

Edit: You can simplify the query (see CSharpie's comment) by extracting .Select(v => v.Value) into

var x = values
    .Where(v => v.Columns.Any(c => c.Code == code && c.Value == value))
    .Concat(values // second alternative if 1st returns empty cursor
       .Where(v => v.Columns.All(c => string.IsNullOrEmpty(c.Code)))
    .Select(v => v.Value) 
    .FirstOrDefault();

Upvotes: 3

Tim Schmelter
Tim Schmelter

Reputation: 460208

You can use DefaultIfEmpty(fallback):

var fallBackValue =  values
    .Where(v =>  v.Columns.All(c => string.IsNullOrEmpty(c.Code))
    .Select(v => v.Value)
    .FirstOrDefault();

var x = values
    .Where(v => v.Columns.Any(c => c.Code == code && c.Value == value))
    .Select(v => v.Value)
    .DefaultIfEmpty(fallBackValue)
    .First(); // FirstOrDefault not nessesary anymore;

This has the advantage that you can even select multiple without breaking the logic, so the fallback value would still be returned if Take(3)(for example) would not return any items.

It is also efficient since the fallback value will be calculated independently of the main query and could be returned from a property, so that it needs to be initialized only once.

Another (similar option) is the null coalescing operator(if Value is a reference type):

var x = values
    .Where(v => v.Columns.Any(c => c.Code == code && c.Value == value))
    .Select(v => v.Value)
    .FirstOrDefault() ?? fallBackValue; 

But i'd prefer the first because it can be chained and also modified easily(i.e. Take(x)).

Upvotes: 1

Related Questions