Reputation: 3154
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
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
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