如何为 Like 创建 System.Linq.Expressions.Expression?

Posted

技术标签:

【中文标题】如何为 Like 创建 System.Linq.Expressions.Expression?【英文标题】:How to create a System.Linq.Expressions.Expression for Like? 【发布时间】:2010-10-31 16:49:57 【问题描述】:

我创建了一个可过滤的 BindingList from this source。效果很好:

list.Filter("Customer == 'Name'");

做它应该做的。内部工作就像一个解析器,它将表达式==!= 转换为System.Linq.Expressions.Expression。在这种情况下,== 变为 System.Linq.Expressions.Expression.Equal

很遗憾System.Linq.Expressions.Expression 不包含like 运算符,我不知道如何解决。

初始代码如下所示:

private static Dictionary<String, Func<Expression, Expression, Expression>> 
    binaryOpFactory = new Dictionary<String, Func<Expression, Expression, Expression>>();

static Init() 
    binaryOpFactory.Add("==", Expression.Equal);
    binaryOpFactory.Add(">", Expression.GreaterThan);
    binaryOpFactory.Add("<", Expression.LessThan);
    binaryOpFactory.Add(">=", Expression.GreaterThanOrEqual);
    binaryOpFactory.Add("<=", Expression.LessThanOrEqual);
    binaryOpFactory.Add("!=", Expression.NotEqual);
    binaryOpFactory.Add("&&", Expression.And);
    binaryOpFactory.Add("||", Expression.Or);

然后我创建了一个表达式来做我想做的事:

private static System.Linq.Expressions.Expression<Func<String, String, bool>>
    Like_Lambda = (item, search) => item.ToLower().Contains(search.ToLower());

private static Func<String, String, bool> Like = Like_Lambda.Compile();

例如

Console.WriteLine(like("McDonalds", "donAld")); // true
Console.WriteLine(like("McDonalds", "King"));   // false

但是binaryOpFactory 需要这个:

Func<Expression, Expression, Expression>

预定义的表达式似乎正是这样:

System.Linq.Expressions.Expression.Or;

谁能告诉我如何转换我的表达方式?

【问题讨论】:

你的 LIKE 是如何运作的?我可以帮助您构建一个表达式,但我需要先了解您希望它如何工作......正则表达式?包含?等等? 没关系。最终的实现可能会使用正则表达式。基本上我有一个 Func ,我传递 2 个字符串并获得 true 或 false 作为返回值。我的问题是我不了解 System.Linq.Expressions.Expression 命名空间中对象的实现,这似乎是 Func (查看 binaryOpFactory 的泛型类型参数)所以我无法创建自己的比较。 重新评论:理解表达式 API 可能需要做一些事情......我尝试在我的博客上介绍一些基础知识; Jon 的书(C# in Depth)也提供了高层次的概述。 【参考方案1】:

类似:

static IEnumerable<T> WhereLike<T>(
        this IEnumerable<T> data,
        string propertyOrFieldName,
        string value)

    var param = Expression.Parameter(typeof(T), "x");
    var body = Expression.Call(
        typeof(Program).GetMethod("Like",
            BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public),
            Expression.PropertyOrField(param, propertyOrFieldName),
            Expression.Constant(value, typeof(string)));
    var lambda = Expression.Lambda<Func<T, bool>>(body, param);
    return data.Where(lambda.Compile());

static bool Like(string a, string b) 
    return a.Contains(b); // just for illustration


Func&lt;Expression,Expression,Expression&gt;而言:

static Expression Like(Expression lhs, Expression rhs)

    return Expression.Call(
        typeof(Program).GetMethod("Like",
            BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
            ,lhs,rhs);

【讨论】:

看起来不错,但我需要……返回一个 Func 但问题是,在这种情况下,表达式 1、表达式 2 和表达式 3 是什么?一个 Expression.Equal 如何在内部工作的例子会很好。 我必须承认,我不了解代码背后的全部魔力,但第二段代码就像一个魅力。 @flem 我不知道;告诉我! @MarcGravell。它确实适用于 LINQ-Entities,但并不理想。自定义“Like”方法未知,因此无法转换为 SQL。结果是此时对查询进行了评估。替代方法(允许转换为 SQL)是直接构建表达式 p =&gt; p.Prop.Contains(val),如下所示:Expression op = Expression.Call(lhs, "Contains", Type.EmptyTypes, rhs);【参考方案2】:

我为IEnumerableIQueryable 创建了两个扩展方法WhereFilter()。 这样,您也可以将此过滤器与例如Entity Framework 和是在服务器上执行的过滤。

我使用了一个基于 * 的过滤器(不是?),所以我可以使用底层 Linq 方法 StartsWith()EndsWith()Contains()。支持的格式:A*、*A、*A*、A*B

用法:

var filtered = list.WhereFilter(i => i.Name, "a*", "First Name");

这里是类的基础知识:

/// <summary>
/// Extension Methods for Filtering on IQueryable and IEnumerable
/// </summary>
internal static class WhereFilterExtensions

    /// <summary>
    /// Filters a sequence of values based on a filter with asterix characters: A*, *A, *A*, A*B
    /// </summary>
    /// <param name="source"></param>
    /// <param name="selector">Field to use for filtering. (E.g: item => item.Name)</param>
    /// <param name="filter">Filter: A*, *A, *A*, A*B</param>
    /// <param name="fieldName">Optional description of filter field used in error messages</param>
    /// <returns>Filtered source</returns>
    public static IEnumerable<T> WhereFilter<T>(this IEnumerable<T> source, Func<T, string> selector, string filter, string fieldName)
    

        if (filter == null)
            return source;

        if (selector == null)
            return source;

        int astrixCount = filter.Count(c => c.Equals('*'));
        if (astrixCount > 2)
            throw new ApplicationException(string.Format("Invalid filter used0. '*' can maximum occur 2 times.", fieldName == null ? "" : " for '" + fieldName + "'"));

        if (filter.Contains("?"))
            throw new ApplicationException(string.Format("Invalid filter used0. '?' is not supported, only '*' is supported.", fieldName == null ? "" : " for '" + fieldName + "'"));


        // *XX*
        if (astrixCount == 2 && filter.Length > 2 && filter.StartsWith("*") && filter.EndsWith("*"))
        
            filter = filter.Replace("*", "");
            return source.Where(item => selector.Invoke(item).Contains(filter));
        

        // *XX
        if (astrixCount == 1 && filter.Length > 1 && filter.StartsWith("*"))
        
            filter = filter.Replace("*", "");
            return source.Where(item => selector.Invoke(item).EndsWith(filter));
        

        // XX*
        if (astrixCount == 1 && filter.Length > 1 && filter.EndsWith("*"))
        
            filter = filter.Replace("*", "");
            return source.Where(item => selector.Invoke(item).StartsWith(filter));
        

        // X*X
        if (astrixCount == 1 && filter.Length > 2 && !filter.StartsWith("*") && !filter.EndsWith("*"))
        
            string startsWith = filter.Substring(0, filter.IndexOf('*'));
            string endsWith = filter.Substring(filter.IndexOf('*') + 1);

            return source.Where(item => selector.Invoke(item).StartsWith(startsWith) && selector.Invoke(item).EndsWith(endsWith));
        

        // XX
        if (astrixCount == 0 && filter.Length > 0)
        
            return source.Where(item => selector.Invoke(item).Equals(filter));
        

        // *
        if (astrixCount == 1 && filter.Length == 1)
            return source;

        // Invalid Filter
        if (astrixCount > 0)            
            throw new ApplicationException(string.Format("Invalid filter used0.", fieldName == null ? "" : " for '" + fieldName + "'"));

        // Empty string: all results
        return source;


    

    /// <summary>
    /// Filters a sequence of values based on a filter with asterix characters: A*, *A, *A*, A*B
    /// </summary>
    /// <param name="source"></param>
    /// <param name="selector">Field to use for filtering. (E.g: item => item.Name)        </param>
    /// <param name="filter">Filter: A*, *A, *A*, A*B</param>
    /// <param name="fieldName">Optional description of filter field used in error messages</param>
    /// <returns>Filtered source</returns>
    public static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, Expression<Func<T, string>> selector, string filter, string fieldName)
    

        if (filter == null)
            return source;

        if (selector == null)
            return source;

        int astrixCount = filter.Count(c => c.Equals('*'));
        if (astrixCount > 2)
            throw new ApplicationException(string.Format("Invalid filter used0. '*' can maximum occur 2 times.", fieldName==null?"":" for '" + fieldName + "'"));

        if (filter.Contains("?"))            
            throw new ApplicationException(string.Format("Invalid filter used0. '?' is not supported, only '*' is supported.", fieldName == null ? "" : " for '" + fieldName + "'"));

        // *XX*
        if (astrixCount == 2 && filter.Length > 2 && filter.StartsWith("*") &&         filter.EndsWith("*"))
        
            filter = filter.Replace("*", "");
            return source.Where(
                Expression.Lambda<Func<T, bool>>(
                    Expression.Call(selector.Body, "Contains", null,  Expression.Constant(filter)),
                    selector.Parameters[0]
                )
            );
        

        // *XX
        if (astrixCount == 1 && filter.Length > 1 && filter.StartsWith("*"))
        
            filter = filter.Replace("*", "");
            return source.Where(
                Expression.Lambda<Func<T, bool>>(
                    Expression.Call(selector.Body, "EndsWith", null, Expression.Constant(filter)),
                    selector.Parameters[0]
                )
            );
        

        // XX*
        if (astrixCount == 1 && filter.Length > 1 && filter.EndsWith("*"))
        
            filter = filter.Replace("*", "");
            return source.Where(
                Expression.Lambda<Func<T, bool>>(
                    Expression.Call(selector.Body, "StartsWith", null,         Expression.Constant(filter)),
                    selector.Parameters[0]
                )
            );
        

        // X*X
        if (astrixCount == 1 && filter.Length > 2 && !filter.StartsWith("*") && !filter.EndsWith("*"))
        
            string startsWith = filter.Substring(0, filter.IndexOf('*'));
            string endsWith = filter.Substring(filter.IndexOf('*') + 1);

            return source.Where(
                Expression.Lambda<Func<T, bool>>(
                    Expression.Call(selector.Body, "StartsWith", null,         Expression.Constant(startsWith)),
                    selector.Parameters[0]
                )
            ).Where(
                Expression.Lambda<Func<T, bool>>(
                    Expression.Call(selector.Body, "EndsWith", null,         Expression.Constant(endsWith)),
                    selector.Parameters[0]
                )
            );
        

        // XX
        if (astrixCount == 0 && filter.Length > 0)
        
            return source.Where(
                Expression.Lambda<Func<T, bool>>(
                    Expression.Equal(selector.Body, Expression.Constant(filter)),
                    selector.Parameters[0]
                )
            );
        

        // *
        if (astrixCount == 1 && filter.Length == 1)
            return source;

        // Invalid Filter
        if (astrixCount > 0)
            throw new ApplicationException(string.Format("Invalid filter used0.", fieldName == null ? "" : " for '" + fieldName + "'"));

        // Empty string: all results
        return source;

    

【讨论】:

by '与例如实体框架和在服务器上执行的过滤'你的意思是在数据库 - 即:翻译成 SQL 吗?我的印象是selector.Invoke 阻止了 EF 转换为 SQL? 是的,ExpressionTree 由“LINQ Provider”转换。使用 Enity Framework 时,LINQ to Entities 提供程序会将其转换为将在数据库上执行的 SQL 字符串。结果将不是数组,而是一个遍历 DataReader 的 IEnumerable。

以上是关于如何为 Like 创建 System.Linq.Expressions.Expression?的主要内容,如果未能解决你的问题,请参考以下文章

UIAlertView-like UIWindow

如何为 django Q 对象动态加入多个参数

如何为 iPhone 分发证书创建私钥?

Magento:如何为部分付款创建模块?

如何为“日期”创建索引?

如何为方法创建一个新类?