I need to check if all definitions contains some specific data. It works fine except the case when GroupBy returns empty collection.
var exist = dbContext.De
Here is another trick:
var exist = dbContext.Definitions
.Where(x => propertyTypeIds.Contains(x.PropertyTypeId) && x.CountryId == countryId)
.GroupBy(x => x.PropertyTypeId)
.Min(some_condition ? (int?)1 : 0) == 1;
It utilizes the fact that the above Min<int?>
method returns:
(A)
null
when the set is empty
(B)0
if the condition is not satisfied for some element
(C)1
if the condition is satisfied for all elements
so we simple check the result for (C) using the nullable value comparison rules.
What about writing your own extension method? (I am pretty sure you will name it better)
public static bool NotEmptyAll<T>(
this IEnumerable<T> collection,
Func<T, bool> predicate)
{
return collection != null
&& collection.Any()
&& collection.All(predicate);
}
Then call it instead of All
var exist = definitions.Where(
x => propertyTypeIds.Contains(x.PropertyTypeId) && x.CountryId == countryId)
.GroupBy(x => x.PropertyTypeId)
.NotEmptyAll(
...some condition...));
Edit: first answer wouldn't have worked.
If you rearrange your query somewhat, you can use DefaultIfEmpty
without needing to change your condition:
var exist = dbContext.Definitions
.Where(x => propertyTypeIds.Contains(x.PropertyTypeId)
&& x.CountryId == countryId)
.GroupBy(x => x.PropertyTypeId);
// apply the condition to all entries,
// resulting in sequence of bools (or empty),
// to permit the next step
.Select(...some condition...)
//if seq is empty, add `false`
.DefaultIfEmpty(false)
//All with identity function to apply the query and calculate result
.All(b => b)
);
Here's the alternative to All
that returns false
if collection is empty:
var collection = Enumerable.Range(0, 0); //empty collection
collection
.Select(IsValid)
.DefaultIfEmpty(false)
.All(b => b);
Or as an extension method:
public static bool AnyAndAll<T>(IEnumerable<T> collection, Func<T, bool> predicate) =>
collection
.Select(predicate)
.DefaultIfEmpty(false)
.All(b => b);
If you're using LINQ to Objects, I'd just write my own extension method. My Edulinq project has sample code for All, and adapting that is pretty simple:
public static bool AnyAndAll<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}
bool any = false;
foreach (TSource item in source)
{
any = true;
if (!predicate(item))
{
return false;
}
}
return any;
}
This avoids evaluating the input more than once.
You may be able to do this using an Aggregate
, along the lines of:
.Aggregate(new {exists = 0, matches = 0}, (a, g) =>
new {exists = a.exists + 1, matches = a.matches + g > 10 ? 1 : 0})
(Here, g > 10
is my test)
And then simple logic that exists
is greater than zero and that exists
and matches
have the same value.
This avoids running the whole query twice.