实现 "IndexOf" 忽略大小写的 C# 动态 lambda 问题
C# Dynamic lambda problem with implementing "IndexOf" ignore case
我一直在关注 pashov.net,尤其是他通过构建动态 Linq 表达式进行过滤的方法。
我实现了它并且有效,但是字符串搜索区分大小写。目前它没有 IndexOf StringComparison.OrdinalIgnoreCase 选项,所以我尝试添加一个。
一旦它到达它试图 运行 对其进行 lambda 调用的那部分代码,我就得到了一个错误... return Expression.Lambda<Func<T, bool>>(exp, param);
从 int32 转换为 bool 时遇到问题。
System.ArgumentException
HResult=0x80070057
Message=Expression of type 'System.Int32' cannot be used for return type 'System.Boolean'
Source=System.Linq.Expressions
StackTrace:
at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters, String paramName)
at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, Boolean tailCall, IEnumerable`1 parameters)
at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, ParameterExpression[] parameters)
at JobsLedger.API.ControllerServices.Shared.OrderAndFIlterHelpers.DynamicFilteringHelper.ConstructAndExpressionTree[T](List`1 filters) in C:\AURELIA.0 - JobsLedgerSPA -ASPNET CORE 3.0\JobsLedger.API\ControllerServices\Shared\OrderAndFIlterHelpers\DynamicFilteringHelper.cs:line 48
at JobsLedger.API.ControllerServices.Shared.ODataFilterAndSort.ParginatedFilteredSorted[T](IQueryable`1 source, String query) in C:\AURELIA.0 - JobsLedgerSPA -ASPNET CORE 3.0\JobsLedger.API\ControllerServices\Shared\OrderAndFIlterHelpers\ODataFilterAndSort.cs:line 41
at JobsLedger.API.ControllerServices.API.App.ClientServices.ClientServices.GetPaginatedClients(String query) in C:\AURELIA.0 - JobsLedgerSPA -ASPNET CORE 3.0\JobsLedger.API\ControllerServices\API\App\ClientServices\ClientServices.cs:line 65
at JobsLedger.API.Controllers.API.App.ClientController.Index(String query) in C:\AURELIA.0 - JobsLedgerSPA -ASPNET CORE 3.0\JobsLedger.API\Controllers\API\App\ClientController.cs:line 28
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
我做了一些 research,有人建议使用 .Convert
进行转换,但这没有用,因为没有从 int32 到 bool 的转换。
这是代码。
public static Expression<Func<T, bool>> ConstructAndExpressionTree<T>(List<ExpressionFilter> filters) {
if (filters.Count == 0)
return null;
ParameterExpression param = Expression.Parameter(typeof(T), "t");
Expression exp = null;
if (filters.Count == 1) {
exp = ExpressionRetriever.GetExpression<T>(param, filters[0]);
}
else {
exp = ExpressionRetriever.GetExpression<T>(param, filters[0]);
for (int i = 1; i < filters.Count; i++) {
exp = Expression.And(exp, ExpressionRetriever.GetExpression<T>(param, filters[i]));
}
}
return Expression.Lambda<Func<T, bool>>(exp, param); //.. FAILS HERE
}
public static class ExpressionRetriever {
private static MethodInfo containsMethod = typeof(string).GetMethod("Contains");
private static MethodInfo startsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
private static MethodInfo endsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) });
public static Expression GetExpression<T>(ParameterExpression param, ExpressionFilter filter) {
MemberExpression member = Expression.Property(param, filter.PropertyName);
ConstantExpression constant = Expression.Constant(filter.Value);
switch (filter.Comparison) {
case Comparison.Equal:
return Expression.Equal(member, constant);
case Comparison.GreaterThan:
return Expression.GreaterThan(member, constant);
case Comparison.GreaterThanOrEqual:
return Expression.GreaterThanOrEqual(member, constant);
case Comparison.LessThan:
return Expression.LessThan(member, constant);
case Comparison.LessThanOrEqual:
return Expression.LessThanOrEqual(member, constant);
case Comparison.NotEqual:
return Expression.NotEqual(member, constant);
case Comparison.Contains:
return Expression.Call(member, containsMethod, constant);
case Comparison.StartsWith:
return Expression.Call(member, startsWithMethod, constant);
case Comparison.IndexOf:
var test = Expression.Call(member, "IndexOf", null, Expression.Constant(filter.Value, typeof(string)), Expression.Constant(StringComparison.InvariantCultureIgnoreCase));
return test;
//return Expression.Convert(test, typeof(bool));
case Comparison.EndsWith:
return Expression.Call(member, endsWithMethod, constant);
default:
return null;
}
}
}
如果我可以放入一些不需要像我所做的那样创建调用的代码,那就太好了。有没有办法根据其他字符串选项将其实现为方法等,或者考虑到我需要一个额外的参数,这是我尝试的唯一方法..
即如何实现 IndexOf 选项和 return 布尔值?
在 .NET Core 中有一个 Contains 的重载,它允许指定比较规则。在那里你可能会得到类似的东西:
public static class ExpressionRetriever
{
private static MethodInfo containsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string), typeof(StringComparison)});
...
public static Expression GetExpression<T>(ParameterExpression param, ExpressionFilter filter)
{
...
ConstantExpression comparisonType = Expression.Constant(StringComparison.OrdinalIgnoreCase);
switch (filter.Comparison)
{
...
case Comparison.Contains:
return Expression.Call(member, containsMethod, constant), comparisonType);
}
}
}
在任何其他情况下,您可以创建一个在表达式中使用的自定义比较方法,例如:
public class StringExtensions
{
public static bool ContainsIgnoringCase(string str, string substring)
{
return str.IndexOf(substring, StringComparison.OrdinalIgnoreCase) >= 0;
}
}
...
public static class ExpressionRetriever
{
...
private static MethodInfo containsIgnoringCaseMethod = typeof(StringExtensions).GetMethod("ContainsIgnoringCase", new Type[] { typeof(string), typeof(string)});
public static Expression GetExpression<T>(ParameterExpression param, ExpressionFilter filter)
{
...
switch (filter.Comparison)
{
...
case Comparison.Contains:
return Expression.Call(null, containsIgnoringCaseMethod, member, constant);
}
}
}
来自 IndexOf
的文档,方法 returns:
The index position of the value parameter if that string is found, or -1 if it is not.
因此,为了检查 Foo.PropertyName
是否包含 Value
,您需要创建以下表达式:
Foo.PropertyName.IndexOf(Value, StringComparison.InvariantCultureIgnoreCase) != -1
这意味着将您目前拥有的所有内容包装在 Expression.NotEqual(left, right)
:
case Comparison.IndexOf:
return Expression.NotEqual(
Expression.Call(
member,
"IndexOf",
null,
Expression.Constant(filter.Value, typeof(string)),
Expression.Constant(StringComparison.InvariantCultureIgnoreCase, typeof(StringComparison))
),
Expression.Constant(-1, typeof(int))
);
我一直在关注 pashov.net,尤其是他通过构建动态 Linq 表达式进行过滤的方法。
我实现了它并且有效,但是字符串搜索区分大小写。目前它没有 IndexOf StringComparison.OrdinalIgnoreCase 选项,所以我尝试添加一个。
一旦它到达它试图 运行 对其进行 lambda 调用的那部分代码,我就得到了一个错误... return Expression.Lambda<Func<T, bool>>(exp, param);
从 int32 转换为 bool 时遇到问题。
System.ArgumentException
HResult=0x80070057
Message=Expression of type 'System.Int32' cannot be used for return type 'System.Boolean'
Source=System.Linq.Expressions
StackTrace:
at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters, String paramName)
at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, Boolean tailCall, IEnumerable`1 parameters)
at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, ParameterExpression[] parameters)
at JobsLedger.API.ControllerServices.Shared.OrderAndFIlterHelpers.DynamicFilteringHelper.ConstructAndExpressionTree[T](List`1 filters) in C:\AURELIA.0 - JobsLedgerSPA -ASPNET CORE 3.0\JobsLedger.API\ControllerServices\Shared\OrderAndFIlterHelpers\DynamicFilteringHelper.cs:line 48
at JobsLedger.API.ControllerServices.Shared.ODataFilterAndSort.ParginatedFilteredSorted[T](IQueryable`1 source, String query) in C:\AURELIA.0 - JobsLedgerSPA -ASPNET CORE 3.0\JobsLedger.API\ControllerServices\Shared\OrderAndFIlterHelpers\ODataFilterAndSort.cs:line 41
at JobsLedger.API.ControllerServices.API.App.ClientServices.ClientServices.GetPaginatedClients(String query) in C:\AURELIA.0 - JobsLedgerSPA -ASPNET CORE 3.0\JobsLedger.API\ControllerServices\API\App\ClientServices\ClientServices.cs:line 65
at JobsLedger.API.Controllers.API.App.ClientController.Index(String query) in C:\AURELIA.0 - JobsLedgerSPA -ASPNET CORE 3.0\JobsLedger.API\Controllers\API\App\ClientController.cs:line 28
at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
我做了一些 research,有人建议使用 .Convert
进行转换,但这没有用,因为没有从 int32 到 bool 的转换。
这是代码。
public static Expression<Func<T, bool>> ConstructAndExpressionTree<T>(List<ExpressionFilter> filters) {
if (filters.Count == 0)
return null;
ParameterExpression param = Expression.Parameter(typeof(T), "t");
Expression exp = null;
if (filters.Count == 1) {
exp = ExpressionRetriever.GetExpression<T>(param, filters[0]);
}
else {
exp = ExpressionRetriever.GetExpression<T>(param, filters[0]);
for (int i = 1; i < filters.Count; i++) {
exp = Expression.And(exp, ExpressionRetriever.GetExpression<T>(param, filters[i]));
}
}
return Expression.Lambda<Func<T, bool>>(exp, param); //.. FAILS HERE
}
public static class ExpressionRetriever {
private static MethodInfo containsMethod = typeof(string).GetMethod("Contains");
private static MethodInfo startsWithMethod = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
private static MethodInfo endsWithMethod = typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) });
public static Expression GetExpression<T>(ParameterExpression param, ExpressionFilter filter) {
MemberExpression member = Expression.Property(param, filter.PropertyName);
ConstantExpression constant = Expression.Constant(filter.Value);
switch (filter.Comparison) {
case Comparison.Equal:
return Expression.Equal(member, constant);
case Comparison.GreaterThan:
return Expression.GreaterThan(member, constant);
case Comparison.GreaterThanOrEqual:
return Expression.GreaterThanOrEqual(member, constant);
case Comparison.LessThan:
return Expression.LessThan(member, constant);
case Comparison.LessThanOrEqual:
return Expression.LessThanOrEqual(member, constant);
case Comparison.NotEqual:
return Expression.NotEqual(member, constant);
case Comparison.Contains:
return Expression.Call(member, containsMethod, constant);
case Comparison.StartsWith:
return Expression.Call(member, startsWithMethod, constant);
case Comparison.IndexOf:
var test = Expression.Call(member, "IndexOf", null, Expression.Constant(filter.Value, typeof(string)), Expression.Constant(StringComparison.InvariantCultureIgnoreCase));
return test;
//return Expression.Convert(test, typeof(bool));
case Comparison.EndsWith:
return Expression.Call(member, endsWithMethod, constant);
default:
return null;
}
}
}
如果我可以放入一些不需要像我所做的那样创建调用的代码,那就太好了。有没有办法根据其他字符串选项将其实现为方法等,或者考虑到我需要一个额外的参数,这是我尝试的唯一方法..
即如何实现 IndexOf 选项和 return 布尔值?
在 .NET Core 中有一个 Contains 的重载,它允许指定比较规则。在那里你可能会得到类似的东西:
public static class ExpressionRetriever
{
private static MethodInfo containsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string), typeof(StringComparison)});
...
public static Expression GetExpression<T>(ParameterExpression param, ExpressionFilter filter)
{
...
ConstantExpression comparisonType = Expression.Constant(StringComparison.OrdinalIgnoreCase);
switch (filter.Comparison)
{
...
case Comparison.Contains:
return Expression.Call(member, containsMethod, constant), comparisonType);
}
}
}
在任何其他情况下,您可以创建一个在表达式中使用的自定义比较方法,例如:
public class StringExtensions
{
public static bool ContainsIgnoringCase(string str, string substring)
{
return str.IndexOf(substring, StringComparison.OrdinalIgnoreCase) >= 0;
}
}
...
public static class ExpressionRetriever
{
...
private static MethodInfo containsIgnoringCaseMethod = typeof(StringExtensions).GetMethod("ContainsIgnoringCase", new Type[] { typeof(string), typeof(string)});
public static Expression GetExpression<T>(ParameterExpression param, ExpressionFilter filter)
{
...
switch (filter.Comparison)
{
...
case Comparison.Contains:
return Expression.Call(null, containsIgnoringCaseMethod, member, constant);
}
}
}
来自 IndexOf
的文档,方法 returns:
The index position of the value parameter if that string is found, or -1 if it is not.
因此,为了检查 Foo.PropertyName
是否包含 Value
,您需要创建以下表达式:
Foo.PropertyName.IndexOf(Value, StringComparison.InvariantCultureIgnoreCase) != -1
这意味着将您目前拥有的所有内容包装在 Expression.NotEqual(left, right)
:
case Comparison.IndexOf:
return Expression.NotEqual(
Expression.Call(
member,
"IndexOf",
null,
Expression.Constant(filter.Value, typeof(string)),
Expression.Constant(StringComparison.InvariantCultureIgnoreCase, typeof(StringComparison))
),
Expression.Constant(-1, typeof(int))
);