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

发布于 2024-07-22 09:39:27 字数 1910 浏览 3 评论 0原文

我创建了一个可过滤的 BindingList 来自此来源。 它工作得很好:

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;

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

I created a filterable BindingList from this source. It works great:

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

does what it should. The internals work like a parser, that converts the expression == or != into System.Linq.Expressions.Expression. In this case, == becomes System.Linq.Expressions.Expression.Equal.

Unfortunately System.Linq.Expressions.Expression does not contain a like operator and I don't know how to solve this.

The initial code looks like this:

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);
}

Then I created an expression that will do what I want:

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();

e.g.

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

But binaryOpFactory requires this:

Func<Expression, Expression, Expression>

The predefined expressions seem to be exactly that:

System.Linq.Expressions.Expression.Or;

Can anyone tell me how to convert my expression?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

淡写薰衣草的香 2024-07-29 09:39:27

类似于:

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 而言:

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

Something like:

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
}

In terms of a Func<Expression,Expression,Expression>:

static Expression Like(Expression lhs, Expression rhs)
{
    return Expression.Call(
        typeof(Program).GetMethod("Like",
            BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
            ,lhs,rhs);
}
白衬杉格子梦 2024-07-29 09:39:27

我为 IEnumerableIQueryable 创建了 2 个扩展方法 WhereFilter()
通过这种方式,您还可以将此过滤器与实体框架一起使用,并且是在服务器上执行的过滤。

我使用了基于 * (不是?)的过滤器,因此我可以使用底层 Linq 方法 StartsWith()EndsWith()包含()。 支持的格式: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 used{0}. '*' can maximum occur 2 times.", fieldName == null ? "" : " for '" + fieldName + "'"));

        if (filter.Contains("?"))
            throw new ApplicationException(string.Format("Invalid filter used{0}. '?' 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 used{0}.", 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 used{0}. '*' can maximum occur 2 times.", fieldName==null?"":" for '" + fieldName + "'"));

        if (filter.Contains("?"))            
            throw new ApplicationException(string.Format("Invalid filter used{0}. '?' 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 used{0}.", fieldName == null ? "" : " for '" + fieldName + "'"));

        // Empty string: all results
        return source;

    }
}

I created 2 extension methods WhereFilter() for IEnumerable and IQueryable.
At this way you can use this filter also with e.g. Entity Framework and is the filtering performed on the server.

I used a filter based on * (not ?) so i could use the underlaying Linq methods StartsWith(), EndsWith() and Contains(). Supported formats: A*, *A, *A*, A*B

Usage:

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

Here the basics of the class:

/// <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 used{0}. '*' can maximum occur 2 times.", fieldName == null ? "" : " for '" + fieldName + "'"));

        if (filter.Contains("?"))
            throw new ApplicationException(string.Format("Invalid filter used{0}. '?' 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 used{0}.", 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 used{0}. '*' can maximum occur 2 times.", fieldName==null?"":" for '" + fieldName + "'"));

        if (filter.Contains("?"))            
            throw new ApplicationException(string.Format("Invalid filter used{0}. '?' 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 used{0}.", fieldName == null ? "" : " for '" + fieldName + "'"));

        // Empty string: all results
        return source;

    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文