使用通用的LINQ表达式调用忽略大小写的Contains方法

6

我正在使用以下代码进行通用过滤,任何搜索文本都会传递,但contains方法区分大小写,我该如何编写以忽略大小写。

public static class QueryExtensions
{
    public static IQueryable<T> Filter<T>(this IQueryable<T> query, string search)    
    {           
        var properties = typeof(T).GetProperties().Where(p => 
                /*p.GetCustomAttributes(typeof(System.Data.Objects.DataClasses.EdmScalarPropertyAttribute),true).Any() && */
                p.PropertyType == typeof(String));        

        var predicate = PredicateBuilder.False<T>();
        foreach (var property in properties )
        {
           predicate = predicate.Or(CreateLike<T>(property,search));
        }
        return query.AsExpandable().Where(predicate);
    }
    private static Expression<Func<T,bool>> CreateLike<T>( PropertyInfo prop, string value)
    {       
        var parameter = Expression.Parameter(typeof(T), "f");
        var propertyAccess = Expression.MakeMemberAccess(parameter, prop);                    
        var like = Expression.Call(propertyAccess, "Contains", null, Expression.Constant(value,typeof(string)));

        return Expression.Lambda<Func<T, bool>>(like, parameter);       
    }

}

你尝试过在比较之前将搜索字符串和要搜索的字符串都转换为特定的大小写吗?例如 if(str1.ToLower() == str2.ToLower()) - Vishweshwar Kapse
4个回答

12

不要调用String.Contains,而是使用不区分大小写的StringComparison参数调用String.IndexOf。然后使用Expression.GreaterThanOrEqual表达式将其结果与0进行比较。您需要在Expression.Call中提供额外的参数作为Expression.Constant。

您可以决定硬编码一个不区分大小写的StringComparison选项,或者将其导出为Filter方法的参数,允许用户决定是否需要不区分大小写的搜索。

您可以像这样做:

    private static Expression<Func<T, bool>> CreateLike<T>(PropertyInfo prop, string value)
    {
        var parameter = Expression.Parameter(typeof(T), "f");
        var propertyAccess = Expression.MakeMemberAccess(parameter, prop);

        var indexOf = Expression.Call(propertyAccess, "IndexOf", null, Expression.Constant(value, typeof(string)),Expression.Constant(StringComparison.InvariantCultureIgnoreCase));
        var like=Expression.GreaterThanOrEqual(indexOf, Expression.Constant(0));
        return Expression.Lambda<Func<T, bool>>(like, parameter);
    }

或者,使用StringComparison参数
    private static Expression<Func<T, bool>> CreateLike<T>(PropertyInfo prop, 
        string value, 
        StringComparison comparison=StringComparison.InvariantCultureIgnoreCase)
    {
        var parameter = Expression.Parameter(typeof(T), "f");
        var propertyAccess = Expression.MakeMemberAccess(parameter, prop);

        var indexOf = Expression.Call(propertyAccess, "IndexOf", null, 
            Expression.Constant(value, typeof(string)),
            Expression.Constant(comparison));
        var like=Expression.GreaterThanOrEqual(indexOf, Expression.Constant(0));
        return Expression.Lambda<Func<T, bool>>(like, parameter);
    }

通过为comparison使用默认值,您避免了为同一作业创建两个重载的情况。

太棒了!我一早上都在苦苦挣扎,而你却把它轻松地呈现给我。 :) - erroric

2
如果您想从列表中过滤或搜索值,请参考以下代码。此外,这是一种通用方法,可帮助您过滤列表中的任何类型的类或对象。它像 SQL 中的 like 子句一样工作,例如 (column1 like '%abc%' or column2 like '%abc%')。请注意保留 HTML 标签。
public static class Filter<T>
{
    public static Expression<Func<T, bool>> FilterExpression(string searchValue)
    {
        Expression finalExpression = Expression.Constant(false);
        var parameter = Expression.Parameter(typeof(T), "x");
        PropertyInfo[] propertyInfos = typeof(T).GetProperties();

            foreach (PropertyInfo propertyInfo in propertyInfos)
            {
                if (propertyInfo.PropertyType == typeof(string))
                {
                    var propertyExpn = Expression.Property(parameter, propertyInfo.Name.Trim().ToLower());
                    var containsExpn = Expression.Call(propertyExpn, "Contains", null, Expression.Constant(searchValue, typeof(string)), Expression.Constant(StringComparison.InvariantCultureIgnoreCase));
                    var nullCheckExpn = Expression.NotEqual(propertyExpn, Expression.Constant(null, typeof(string)));
                    var andAlsoExpn = Expression.AndAlso(nullCheckExpn, containsExpn);
                    finalExpression = Expression.Or(finalExpression, andAlsoExpn);
                }
            }
            var rowFilterLambdaExpression = Expression.Lambda<Func<T, bool>>(finalExpression, new ParameterExpression[] { parameter });
            return rowFilterLambdaExpression;
    }
}

使用示例:

var result = 
 dataItems.Where(Filter<T>.FilterExpression(model.FilterValue).Compile()).ToList();

嗨,Jeremy,感谢您改进和格式化我的答案。非常感谢您的帮助。 - Ganesh Madane

1
您可以尝试使用 String.IndexOf 代替。
string x,y = string.Empty;
x.IndexOf(y,0,x.Length, StringComparison.CurrentCultureIgnoreCase) > -1

由于它具有StringComparison参数,因此这将返回一个整数。
这将返回一个整数。
var like = Expression.Call(propertyAccess, "IndexOf", null, Expression.Constant(value, typeof(string)), Expression.Constant(StringComparison.CurrentCultureIgnoreCase,typeof(StringComparison)));

但是我该如何在Expression.Call中使用这行代码 Expression.Call(expression, "Contains", null, Expression.Constant(filterText, typeof (string))); - ineffable p
@ineffablep 你可以将like表达式包裹在另一个函数中,该函数执行“>”比较。我在创建通用过滤方面并没有做太多工作。如果你可以等待9个小时,我下班回家后可以给出一个更好的答案 ;) - ywm

-1

最简单的方法可能是先将两个参数都转换为大写(在.NET中,大写转换比小写转换更优化)。然后再进行比较。

可以像这样进行大写转换:

var expression = Expression.Call(property, typeof(string).GetMethod("ToUpperInvariant", System.Type.EmptyTypes));

最好的做法是根本不进行任何转换。使用不考虑大小写的比较方式。这样可以避免创建一次性字符串,通过为每个项目创建和比较两个以上的字符串来损害性能。更不用说避免各种文化中的大小写怪癖了。 - Panagiotis Kanavos
我认为不可能进行一种"不考虑大小写"的大小写不敏感比较。如何在不考虑大小写的情况下比较"Dog"和"dog"(以及像您所说的文化规则)?但是, 对于高性能情况而言, 您提出的关于不必要的字符串分配的观点是很好的, 非常感谢-没有必要给GC额外的工作。 - pattermeister
你可以让框架来处理它。所有接受StringComparison参数的函数都已经实现了这个功能。事实上,调用最终会被路由到适当的CultureInfo对象,该对象显然知道这些规则。ToUpper()也使用CultureInfo来返回正确的大写形式。 - Panagiotis Kanavos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接