Reputation: 46760
This has caused me no end of problems today. I have this simple query
var result =
DataContext.Accommodations.Where(a =>
(criteria.MinPrice == null || a.AccommodationRates.Any(r => r.From >= criteria.MinPrice)) &&
(criteria.MaxPrice == null || a.AccommodationRates.Any(r => r.To <= criteria.MaxPrice)) &&
(criteria.Locations == null || criteria.Locations.Count == 0 || a.AccommodationPlaceJoins.Any(j => criteria.Locations.Contains(j.Place.PlaceName)))
);
The last line of this query is causing me problems
(criteria.Locations == null ||
criteria.Locations.Count == 0 ||
a.AccommodationPlaceJoins.Any(j => criteria.Locations.Contains(j.Place.PlaceName)))
The error it gives is
Unable to create a constant value of type 'System.Collections.Generic.IList`1'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
I'm not even trying to create a list. All I'm trying to do here is bring back accommodations which are associated to a place (where the place name in the Place table which is linked to the Accommodation table via the AccommodationPlaceJoin table) is equal to any one of the place names in criteria.Locations (which is of type IList).
I've tried changing this line to this, but it didn't work.
(criteria.Locations == null ||
criteria.Locations.Count == 0 ||
a.AccommodationPlaceJoins.Any(j => criteria.Locations.Any(l => l == j.Place.PlaceName)))
Upvotes: 18
Views: 22740
Reputation: 747
I had this same issue when trying to pass a list of strings for comparison into an EF core query recently and found that it resolves the issue to assign an empty array or empty list to the value in case it is null rather than doing the null check right in the EF Core query.
This allows you to query the EF Core database without the null error and you don't need a bunch of conditional branching for separate queries - just one query with a bit of null coalescence.
So your code fixed this way would look like this:
var locations = criteria.Locations == null ? new List<Location>() : criteria.Locations;
var result =
DataContext.Accommodations.Where(a =>
(criteria.MinPrice == null || a.AccommodationRates.Any(r => r.From >= criteria.MinPrice)) &&
(criteria.MaxPrice == null || a.AccommodationRates.Any(r => r.To <= criteria.MaxPrice)) &&
(locations.Any() ? a.AccommodationPlaceJoins.Any(j => criteria.Locations.Contains(j.Place.PlaceName))) : true);
Keep in mind I am not sure of the type for locations or accomodationplacejoins so I cannot be sure the above code will exactly work but the general idea is we are doing a .Any() instead of checking for null. This way we can keep our query a little simpler. We are then using a ternary expression within the last block of our where clause so if the locations list turns out to be empty, we skip the condition entirely and just return true which coalesces the empty list if it is empty.
Upvotes: 0
Reputation: 177163
The constant value EF can't create is null
for the comparison criteria.Locations == null
. You need to split the query into two cases and do the check for the empty list outside of the query, for example like so:
var result = DataContext.Accommodations.Where(a =>
(criteria.MinPrice == null ||
a.AccommodationRates.Any(r => r.From >= criteria.MinPrice)) &&
(criteria.MaxPrice == null ||
a.AccommodationRates.Any(r => r.To <= criteria.MaxPrice)));
if (criteria.Locations != null && criteria.Locations.Count > 0)
{
result = result.Where(a => a.AccommodationPlaceJoins
.Any(j => criteria.Locations.Contains(j.Place.PlaceName)));
}
Edit
BTW: Composing the whole query would make it better readable in my opinion and will simplify the SQL that has to be sent to the database:
IQueryable<Accommodation> result = DataContext.Accommodations;
if (criteria.MinPrice != null)
result = result.Where(a => a.AccommodationRates
.Any(r => r.From >= criteria.MinPrice));
if (criteria.MaxPrice != null)
result = result.Where(a => a.AccommodationRates
.Any(r => r.To <= criteria.MaxPrice));
if (criteria.Locations != null && criteria.Locations.Count > 0)
result = result.Where(a => a.AccommodationPlaceJoins
.Any(j => criteria.Locations.Contains(j.Place.PlaceName)));
Upvotes: 49