Reputation: 3253
I have a list of Countries in a dbContext using EfCore
I want to implement a generic way of filtering this list
bool FilterCountriesv1(Country s)
{
var result = s.Name.StartsWith("A") && s.Name.Length > 4;
return result;
}
private async Task<List<Country>> GetCountriesAsync(Func<Country, bool> func)
{
var allCountries = DbContext.Countries.AsQueryable();
var filteredCountries = allCountries.Where(x=>func(x)).AsQueryable();
var result = await filteredCountries.ToListAsync();
return result;
}
Where I use it like this
var countries = await GetCountriesAsync(FilterCountriesv1);
When I run this I get the error
The LINQ expression 'DbSet<Country>.Where(c => Invoke(__func_0, c[Country])
)' could not be translated.
Either rewrite the query in a form that can be translated,
or switch to client evaluation explicitly by inserting a call to either AsEnumerable(),
AsAsyncEnumerable()
Not sure how to resolve this?
Upvotes: 0
Views: 324
Reputation: 8459
The filter should be a string and convert it to the lambda expression, but make sure the property exists in the passed in model. Try like below:
public List<T> GetFilterResult<T>(IQueryable<T> currentlist, string filter)
{
var exp = DynamicExpressionParser.ParseLambda<T, bool>(ParsingConfig.Default, false, filter, currentlist);
var func = exp.Compile();
var result = currentlist.Where(func).AsQueryable().ToList();
return result;
}
string Filter()
{
string filter = "x => x.Name.StartsWith(\"A\") && x.Name.Length > 4";
return filter;
}
Use it like this:
var countries = _context.Countries.AsQueryable();
var result = GetFilterResult(countries, Filter());
Upvotes: -1
Reputation: 148
As stated by @COLD TOLD you should use an expression
Change your code to retrieve data to this:
private async Task<List<Country>> GetCountriesAsync(Expression<Func<Country, bool>> expression)
{
var allCountries = DbContext.Countries.AsQueryable();
var filteredCountries = allCountries.Where(expression).AsQueryable();
var result = await filteredCountries.ToListAsync();
return result;
}
Change your filter to return an expression
Expression<Func<Country, bool>> FilterCountriesv1(Country s)
{
return (x) => x.Name.StartsWith("A") && x.Name > 4;
// if you want to use s as a filter you can do
// return (x) => x.Name.StartsWith(s.Name) && x.Name > 4
}
And call like this:
var countries = await GetCountriesAsync(FilterCountriesv1(new Country() { Name = "A"} ));
For conversion use this (install nuget package Serialize.Linq)
private string SerializeExpression(Expression expression)
{
string result;
using (MemoryStream stream = new MemoryStream())
using (StreamReader reader = new StreamReader(stream))
{
new ExpressionSerializer(new JsonSerializer()).Serialize(stream, expression);
stream.Position = 0;
result = reader.ReadToEnd();
}
return result;
}
private Expression<Func<T, bool>> DeserializeExpression<T>(string expression)
{
Expression<Func<T, bool>> result;
using (MemoryStream stream = new MemoryStream())
using (StreamWriter writer = new StreamWriter(stream))
{
writer.Write(expression);
writer.Flush();
stream.Position = 0;
result = new ExpressionSerializer(new JsonSerializer()).Deserialize(stream) as Expression<Func<T, bool>>;
}
return result;
}
And then you can do something like this
string expression = SerializeExpression(FilterCountriesv1(new Country() { Name = "A"}));
var countries = dbContext.Countries.Where(DeserializeExpression<Country>(expression)).ToList();
*Note that the JsonSerializer used for serializing and deserializing is the one under Serializers.Linq.Serializers and not the one under Newtonsoft.Json
Upvotes: 0
Reputation: 13579
I do not think you can use pure delegates when trying to execute against sql. EF cant translate you pure delegate to sql.
Try
Expression<Func<Country, bool>> expected = p=> p.Name.StartsWith("A") && p.Name.Length > 4;
Upvotes: 3