Reputation: 1469
I am currently trying to convert this:
users.Where(s => EF.Functions.Like(EF.Functions.Collate(s.Username, "Latin1_General_CI_AI"), $"%{searchString}%"));
into a expression tree function (code below). I got this far using this answer
public static IQueryable<T> IgnoreAccentSearch<T>(this IQueryable<T> queryable, string searchString, string property)
{
// Get our generic object
ParameterExpression entity = Expression.Parameter(typeof(T), "entity");
// get the Collate method from EF.Functions
var efCollatMethod = typeof(RelationalDbFunctionsExtensions)
.GetMethod("Collate")
.MakeGenericMethod(typeof(T));
// Get the Like Method from EF.Functions
var efLikeMethod = typeof(DbFunctionsExtensions).GetMethod("Like",
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
null,
new[] { typeof(DbFunctions), typeof(string), typeof(string) },
null);
// We make a pattern for the search
var pattern = Expression.Constant($"%{searchString}%", typeof(string));
// make the collate constant
var collate = Expression.Constant("Latin1_General_CI_AI", typeof(string));
// Create the collate expression
Expression collateExpression = Expression.Call(efCollatMethod,
Expression.Property(null, typeof(EF), nameof(EF.Functions)), entity, collate);
// Get property from our object
var propertyExpression = Expression.Property(collateExpression, property);
// Сall the method with all the required arguments
Expression whereExpression = Expression.Call(efLikeMethod,
Expression.Property(null, typeof(EF), nameof(EF.Functions)), propertyExpression, pattern);
// Compose and pass the expression to Where
var expression = Expression.Lambda<Func<T, bool>>(whereExpression, entity);
return queryable.Where(expression);
}
I get the following error message:
InvalidOperationException: The LINQ expression 'DbSet() .Where(u => __Functions_0 .Like( matchExpression: __Functions_0 .Collate( operand: u, collation: "Latin1_General_CI_AI").Username, pattern: "%K%"))' 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 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
So I'm doing something wrong. Can anyone help me here? I haven't worked with expression trees a lot before, so any info is welcome.
Upvotes: 2
Views: 432
Reputation: 27282
Usually, if it is possible, I'm trying to force compiler to do major part of work:
public static IQueryable<T> IgnoreAccentSearch<T>(this IQueryable<T> queryable, string searchString, string property)
{
// defining Expression Template with two input parameters
Expression<Func<string, string, bool>> predicateTemplate =
(prop, value) => EF.Functions.Like(EF.Functions.Collate(prop, "Latin1_General_CI_AI"), $"%{value}%"));
var entityParam = Expression.Parameter(typeof(T), "entity");
var propExpr = Expression.PropertyOrField(entityParam, property);
var searchExpr = Expression.Constant(searchString);
var body = predicateTemplate.Body;
// injecting needed values instead of parameters
body = ReplacingExpressionVisitor.Replace(predicateTemplate.Parameters[0], propExpr, body);
body = ReplacingExpressionVisitor.Replace(predicateTemplate.Parameters[1], searchExpr, body);
var lambda = Expression.Lambda<Func<T, bool>>(body, entityParam);
return queryable.Where(lambda);
}
Upvotes: 4