I'm using net5.0 and EntityFrameworkCore 5.0.4.
I have a search method that has optional strings to search for on a DataContext in EFCore.
I want to check if each of the strings is not null or white space.
I could do this:
var query = context.Model.AsQueryable();
if (!string.IsNullOrWhiteSpace(parameters.Id))
{
query = query.Where(x => x.Id.ToLower().Contains(parameters.Id.ToLower()));
}
but that is just horrible to maintain. What I started trying to get going is this:
public static IQueryable<T> FilterBy<T>(this IQueryable<T> query, string? searchValue,
Expression<Func<T, string>> getValueExpression)
{
if (string.IsNullOrWhiteSpace(searchValue?.Trim()))
{
return query;
}
// return with a where clause
}
I got this working but it does not support accessing joins that I need and I get the feeling this is not a good way:
var searchLower = searchValue.Trim().ToLower();
var propertyName = getValueExpression.GetMemberAccess().Name;
return query.Where(x => EF.Property<string?>(x!, propertyName)!.ToLower().Contains(searchLower));
I want to use it like so:
query
.FilterBy(parameters.Id, x => x.Id)
.FilterBy(parameters.Name, x => x.Name)
.FilterBy(parameters.CompanyName, x => x.Company.Name) // access via include/join
private static readonly MethodInfo StringContainsMethod =
typeof(string).GetMethod(nameof(string.Contains), new[] {typeof(string)})!;
private static readonly MethodInfo StringToLowerMethod =
typeof(string).GetMethod(nameof(string.ToLower), Type.EmptyTypes)!;
public static IQueryable<T> FilterBy<T>(this IQueryable<T> query, string? searchValue,
Expression<Func<T, string?>> memberExpression)
{
if (string.IsNullOrWhiteSpace(searchValue))
return query;
var valueExpression = Expression.Constant(searchValue.ToLower());
var toLower = Expression.Call(memberExpression.Body, StringToLowerMethod);
var call = Expression.Call(toLower, StringContainsMethod, valueExpression);
var sourceParam = memberExpression.Parameters.First();
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(call, sourceParam);
return query.Where(predicate);
}
public static IQueryable<T> FilterBy<T>(this IQueryable<T> query, int? filterInteger,
Expression<Func<T, int?>> memberExpression)
{
if (filterInteger == null)
return query;
var valueExpression = Expression.Constant(filterInteger);
var call = Expression.Equal(memberExpression.Body, valueExpression);
var sourceParam = memberExpression.Parameters.First();
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(call, sourceParam);
return query.Where(predicate);
}
public static IQueryable<T> FilterBy<T>(this IQueryable<T> query, bool? filterBoolean,
Expression<Func<T, bool?>> memberExpression)
{
if (filterBoolean == null)
return query;
var valueExpression = Expression.Constant(filterBoolean);
var call = Expression.Equal(memberExpression.Body, valueExpression);
var sourceParam = memberExpression.Parameters.First();
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(call, sourceParam);
return query.Where(predicate);
}
I've not tested this but I believe you'd need to use expressions to achieve this. The following will build an expression to use as the predicate in the Where method:
public static IQueryable<T> FilterBy<T>(
this IQueryable<T> query,
string searchValue,
Expression<Func<T, string>> memberExpression)
{
if (string.IsNullOrWhiteSpace(searchValue))
return query;
// must be a lambda expression
LambdaExpression lambdaExpression = memberExpression as LambdaExpression;
if (lambdaExpression == null)
throw new ArgumentException($"Expression '{memberExpression}' is not a lambda expression.");
// get the member
Func<ParameterExpression, Expression> sourceExpression = source => Expression.Invoke(lambdaExpression, source);
ParameterExpression sourceParameter = Expression.Parameter(typeof(T), "source");
Expression sourceMember = sourceExpression(sourceParameter);
// expression for the search value
ConstantExpression valueExpression = Expression.Constant(searchValue);
// expression to call the Contains method
MethodInfo containsMethod = GetContainsMethod();
MethodCallExpression callExpression = Expression.Call(null, containsMethod, sourceMember, valueExpression);
// predicate expression
Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(callExpression, sourceParameter);
return query.Where(predicate);
}
private static MethodInfo GetContainsMethod()
{
// get method
MethodInfo genericContainsMethod = typeof(Queryable)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.First(m => m.Name == "Contains"
&& m.IsGenericMethod
&& m.GetParameters().Count() == 2);
// apply generic types
MethodInfo containsMethod = genericContainsMethod
.MakeGenericMethod(new Type[] { typeof(string) });
return containsMethod;
}