Transforming if in single LINQ expression - c#

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();

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();

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)).

Related

C# Entity Framework. Simplify query

How to simplify the next query to database:
public Plan? Get(DateTime now)
{
return context.Plans
.Where(x => IsActivePlan(x, now)) // 1 condition
.Where(x => IsPlolongingPlan(x, now)) // 2 condition
.OrderBy(x => x.StartedAt)
.FirstOrDefault();
}
What I need:
If there are objects after 1 condition, then execute "OrderBy" and return the first element. If there are no objects, then go to the 2 condition, execute "OrderBy" and return "FirstOrDefault". I don't want the objects to be taken at once by two conditions.
Thank you
something like this
public Plan? Get(DateTime now)
{
return context.Plans.Where(x => IsActivePlan(x, now)).OrderBy(x => x.StartedAt).FirstOrDefault()
?? context.Plans.Where(x => IsPlolongingPlan(x, now)).OrderBy(x => x.StartedAt).FirstOrDefault();
}
because you don't want two conditions been true at once you have to:
return context.Plans
.Where(x => (IsActivePlan(x, now) && !IsPlolongingPlan(x, now)) ||
(!IsActivePlan(x, now) && IsPlolongingPlan(x, now)))
.OrderBy(x => x.StartedAt)
.FirstOrDefault();
You can check before if exist any
Like this:
var result = null;
if (context.Plans.Any(x => IsActivePlan(x, now)))
{
result = context.Plans.Where(x => IsActivePlan(x, now))
.OrderBy(x => x.StartedAt)
.FirstOrDefault();
}
else
{
result = context.Plans.Where(x => IsPlolongingPlan(x, now))
.OrderBy(x => x.StartedAt)
.FirstOrDefault();
}

Entity Framework include child items with filter

I am currently working with a schema which contains entities, each containing attributes and each of them contain one type specification. I want extract a list which fulfills a specific attribute requirement it being of the type lookup and a specific specification.
I have tried this so far
schemaRepository
.Many()
.OrderByDescending(x => x.Version)
.SelectMany(x => x.Entities
.Where(x => x.Attributes
.Any(y => y.Type == DataType.Lookup &&
y.TypeSpecification.EntityInternalName == "Weather")));
This returns a list of entities which fulfills the requirement, but none of the entities has any attributes?
How do I also include this within the query?
I tried adding include at the end,
var entitiesWithLookupsTolookupEntityName = schemaRepository
.Many()
.OrderByDescending(x => x.Version)
.Include(x=> x.Entities)
.ThenInclude(x=>x.Attributes)
.ThenInclude(x=>x.TypeSpecification)
.SelectMany(x => x.Entities
.Where(x => x.Attributes
.Any(y => y.Type == DataType.Lookup &&
y.TypeSpecification.EntityInternalName == "Weather")));
but this just include all attributes and etc. ignoring the Any condition for the attributes..
How to get around this?
I tried splitting it a bit
var entitiesWithLookupsTolookupEntityName = schemaRepository
.Many()
.OrderByDescending(x => x.Version)
.Take(1)
.SelectMany(x => x.Entities
.Where(y => y.Attributes
.Any(z => z.Type == DataType.Lookup && z.AttributeTypeSpecification.EntityInternalName == lookupEntityName))).AsEnumerable();
IList<ReferenceView> output = new List<ReferenceView>();
foreach (var entity in entitiesWithLookupsTolookupEntityName)
{
var attribute = schemaRepository.Many().OrderByDescending(x => x.Version).First().Entities
.Where(x => x.InternalName == entity.InternalName);
}
but here I get an exception every in for loop

Get parent objects based on child object field value

I need to return all objects that have child objects with a certain field != null.
NOTE: EpicStoryId is nullable int (as in 'int?')
I have tried:
return _context.Features
.Where(x => x.UserStories.Any(us => us.EpicStoryId.HasValue)
&& x.Id == Id)
.FirstOrDefault();
and I have tried:
return _context.Features
.Where(x => x.UserStories.Any(us => us.EpicStoryId != null)
&& x.Id == Id)
.FirstOrDefault();
and for good measure:
return _context.Features
.Where(x => x.UserStories.Any(us => us.EpicStoryId.HasValue == false)
&& x.Id == Id)
.FirstOrDefault();
and finally:
return _context.Features
.Where(x => x.UserStories.Any(us => us.EpicStoryId > 0)
&& x.Id == Id)
.FirstOrDefault();
But none of these work. It's still returning every 'Feature' with Id=Id regardless if a child has a value for EpicStoryId or not. (FYI, I checked the data and there ARE null values for some EpicStoryId's.)
sample data:
Any will return true i any 1 EpicStoryId has value so your your condition is failing.
All should do:-
return _context.Features
.FirstOrDefault(x => x.UserStories.All(us => us.EpicStoryId.HasValue)
&& x.Id == Id);
If you need to return all objects then don't use FirstOrDefault(), use combination of .Where() and .ToList() methods :
For any of us EpicStoryIds are not null use :
return _context.Features
.Where(x => x.Id == Id && x.UserStories.Any(us => us.EpicStoryId.HasValue))
.ToList();
For all of us EpicStoryIds are not null you can use :
return _context.Features
.Where(x => x.Id == Id && x.UserStories.All(us => us.EpicStoryId.HasValue))
.ToList();
If you want to return list of UserStories and not Features, you can use :
return _context.Features
.Where(x => x.Id == Id)
.SelectMany(x => x.UserStories
.Where(us => us.EpicStoryId.HasValue))
.ToList();

Include datatable in existing linq query

I have the below piece of code which gives my time zones as per each country
var zones = TzdbDateTimeZoneSource.Default
.ZoneLocations
.Where(x => x.CountryCode == countryCode)
.Select(x => x.ZoneId);
Now i need to include datatable within the above linq query to check if that datatable have the zone locations in database already as explained in below code. How do i do that ?
var zones = TzdbDateTimeZoneSource.Default
.ZoneLocations
.Where(x => x.CountryCode == countryCode &&
oratznamesoratznames.Select().ToList()
.Exists(row => row["TZNAME"].ToString().ToUpper() == x.ZoneId))
.Select(x => x.ZoneId);
Id I understand your question correcty, you could use .Any(). It returns a boolean indicating where any of the elements of the collection matches the predicate.
Also, note that there is no need to use && in this case.
var zones = TzdbDateTimeZoneSource.Default
.ZoneLocations
.Where(x => x.CountryCode == countryCode)
.Select(x => x.ZoneId)
.Where(x => oratznamesoratznames
.Any(r => r["TZNAME"].ToString().ToUpper() == x))

LINQ OrderBy/ThenBy ChildrenCollection.SortOrder

EDIT:
Both queries in this example are not ordering by the location sort order. Does anyone have any suggestions on how to do this?
I need some help with ordering an entity collection of groups by a child entity column. I have a one to many relationship from groups to locations.
I need to get the groups ordered by the group name and then by the location sort order.
Since the child entity is a collection I am having trouble.
Here is code I am using that works but returns duplicates:
var groups = db.GetNavigationGroups()
.Where(g => selectedLocation > 0 ? g.NavigationGroupLocations.Any(l => l.Location == selectedLocation) : true)
.SelectMany(g => g.NavigationGroupLocations, (g, l) => new { g, l })
.OrderBy(x => x.g.Name)
.ThenBy(x => x.l.SortOrder)
.Select(x => x.g);
Then I tried this approach using FirstOrDefault():
List<NavigationGroup> groups = db.DataModel.NavigationGroups
.Where(g => selectedLocation > 0 ? g.NavigationGroupLocations.Any(l => l.Location == selectedLocation) : true)
.OrderBy(g => g.Name)
.ThenBy(g => g.NavigationGroupLocations.Where(l => l.Location == selectedLocation && l.GroupID == g.ID).OrderBy(l => l.SortOrder).FirstOrDefault())
.ToList();
The problem is that I cannot seem to get the groups in the order I need them to be in based on the locations SortOrder column.
The second query you have looks like you're trying to sort by the NavigationGroupLocations object instead of on it's fields. Have you tried:
List<NavigationGroup> groups = db.DataModel.NavigationGroups
.Where(g => selectedLocation > 0 ? g.NavigationGroupLocations.Any(l => l.Location == selectedLocation) : true)
.OrderBy(g => g.Name)
.ThenBy(g => g.NavigationGroupLocations.Where(l => l.Location == selectedLocation && l.GroupID == g.ID).OrderBy(l => l.SortOrder).FirstOrDefault().SortOrder)
.ToList();
You might also want to add your selectedLocation condition to the order clause.
List<NavigationGroup> groups = db.DataModel.NavigationGroups
.Where(g => selectedLocation > 0 ? g.NavigationGroupLocations.Any(l => l.Location == selectedLocation) : true)
.OrderBy(g => g.Name)
.ThenBy(g => g.NavigationGroupLocations.Where(l => selectedLocation > 0 ? l.Location == selectedLocation : true && l.GroupID == g.ID).OrderBy(l => l.SortOrder).FirstOrDefault().SortOrder)
.ToList();

Categories