EFCore 中带有动态 where 子句的 LINQ
LINQ with dynamic where clause in EFCore
我正在使用 net5.0 和 EntityFrameworkCore 5.0.4。
我有一个搜索方法,该方法具有可选的字符串以在 EFCore 中的 DataContext 上搜索。
我想检查每个字符串是否不为空或不为空 space。
我能做到:
var query = context.Model.AsQueryable();
if (!string.IsNullOrWhiteSpace(parameters.Id))
{
query = query.Where(x => x.Id.ToLower().Contains(parameters.Id.ToLower()));
}
但这很难维护。我开始尝试的是:
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
}
我可以使用它,但它不支持访问我需要的连接,我觉得这不是一个好方法:
var searchLower = searchValue.Trim().ToLower();
var propertyName = getValueExpression.GetMemberAccess().Name;
return query.Where(x => EF.Property<string?>(x!, propertyName)!.ToLower().Contains(searchLower));
我想这样使用它:
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);
}
我没有对此进行测试,但我相信您需要使用表达式来实现这一点。以下将构建一个表达式,用作 Where 方法中的谓词:
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;
}
我正在使用 net5.0 和 EntityFrameworkCore 5.0.4。
我有一个搜索方法,该方法具有可选的字符串以在 EFCore 中的 DataContext 上搜索。
我想检查每个字符串是否不为空或不为空 space。
我能做到:
var query = context.Model.AsQueryable();
if (!string.IsNullOrWhiteSpace(parameters.Id))
{
query = query.Where(x => x.Id.ToLower().Contains(parameters.Id.ToLower()));
}
但这很难维护。我开始尝试的是:
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
}
我可以使用它,但它不支持访问我需要的连接,我觉得这不是一个好方法:
var searchLower = searchValue.Trim().ToLower();
var propertyName = getValueExpression.GetMemberAccess().Name;
return query.Where(x => EF.Property<string?>(x!, propertyName)!.ToLower().Contains(searchLower));
我想这样使用它:
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);
}
我没有对此进行测试,但我相信您需要使用表达式来实现这一点。以下将构建一个表达式,用作 Where 方法中的谓词:
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;
}