用于创建表达式的 PredicateBuilder 辅助函数

PredicateBuilder helper function for create expression

我有一个用于搜索条件的表单,我使用 PredicateBuilder 将所有条件组合到一个 WHere 表达式中 - 并且 EF 生成 sql 用于在数据库端进行评估。

为了允许用户在相等、开头、结尾和包含之间进行选择,我使用星号通配符。

这是我的代码:

var predicate = LinqKit.PredicateBuilder.New<PersonEntity>(true);


{
    var arg = parameters.Name;
    arg = arg.Trim();

    var start = arg[0] == '*';
    var end = arg[arg.Length - 1] == '*';

    arg = arg.Trim('*');

    if (start && end)
        predicate.And(x => x.Name.Contains(arg));
    else if (start)
        predicate.And(x => x.Name.StartsWith(arg));
    else if (end)
        predicate.And(x => x.Name.EndsWith(arg));
    else
        predicate.And(x => x.Name == arg);
}

{
    var arg = parameters.Address;
    arg = arg.Trim();

    var start = arg[0] == '*';
    var end = arg[arg.Length - 1] == '*';

    arg = arg.Trim('*');

    if (start && end)
        predicate.And(x => x.Address.Contains(arg));
    else if (start)
        predicate.And(x => x.Address.StartsWith(arg));
    else if (end)
        predicate.And(x => x.Address.EndsWith(arg));
    else
        predicate.And(x => x.Address == arg);
}

结束等...

我想写一个通用的辅助函数,简单使用:

predicate.And(Helper.AddStringCompareCriteria(x => x.Name, parameters.Name);
predicate.And(Helper.AddStringCompareCriteria(x => x.Address, parameters.Address);

我现在的尝试:

public static class Helper
{
    static Type strType = typeof(string);
    static MethodInfo strStart = typeof(string).GetMethod("StartsWith");
    static MethodInfo strEnd = typeof(string).GetMethod("EndsWith");
    static MethodInfo strContains = typeof(string).GetMethod("Contains");
    static MethodInfo strEquals = typeof(string).GetMethod("Equals");

    static MethodInfo RightMethod(bool start, bool end)
    {
        if (start && end)
            return strContains;
        if (start)
            return strStart;
        if (end)
            return strEnd;
        else
            return strEquals;
    }

    public static Expression<Func<T, bool>> AddStringCompareCriteria<T, TResult>(Expression<Func<T, TResult>> member, string toComprae)
    {
        var arg = toComprae;
        arg = arg.Trim();

        var start = arg[0] == '*';
        var end = arg[arg.Length - 1] == '*';
        arg = arg.Trim('*');

        MethodInfo method = RightMethod(start, end);

        ParameterExpression entityParam = Expression.Parameter(typeof(T), "entity");


        return Expression.Lambda<Func<T, bool>>(
            Expression.Call(/* get-member-here?*/ , method, new[] { Expression.Constant(arg) }),
            entityParam);
    }
}

我现在不知道如何访问选定的成员(通过函数表达式), 我不确定我的方向是否正确,我很乐意提供帮助!

所以首先,您需要删除通用参数 TResult,因为您的代码 要求 它是一个字符串。只需将该类型的所有用法替换为 string,因为任何其他类型都不适用于此方法。

接下来,与其尝试构建整个表达式 "by hand",不如构建一个接受字符串并使用适当的比较函数计算布尔值的表达式,然后仅组合该表达式使用提供的表达式。一般来说,我会说只要可以可能 避免,就应该避免手动构建表达式。您的代码将更短、更容易理解,而且重要的是 由编译器静态验证

所以我们首先将您的 RightMethod 调整为 return 和 Expression:

static Expression<Func<string, bool>> ComparisonExpression(bool start, bool end, string toCompare)
{
    if (start && end)
        return value => value.Contains(toCompare);
    if (start)
        return value => value.StartsWith(toCompare);
    if (end)
        return value => value.EndsWith(toCompare);
    else
        return value => value.Equals(toCompare);
}

接下来我们需要一种将两个表达式组合在一起的方法。这里我们要手工搭建,但是可以写成任意两个表达式,这样我们就不用每次需要两个表达式都重新写代码了合并。

public static Expression<Func<TSource, TResult>> Compose<TSource, TIntermediate, TResult>(
    this Expression<Func<TSource, TIntermediate>> first,
    Expression<Func<TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TSource));
    var intermediateValue = first.Body.ReplaceParameter(first.Parameters[0], param);
    var body = second.Body.ReplaceParameter(second.Parameters[0], intermediateValue);
    return Expression.Lambda<Func<TSource, TResult>>(body, param);
}

其中使用如下代码实际替换参数:

public static Expression ReplaceParameter(this Expression expression,
    ParameterExpression toReplace,
    Expression newExpression)
{
    return new ParameterReplaceVisitor(toReplace, newExpression)
        .Visit(expression);
}
public class ParameterReplaceVisitor : ExpressionVisitor
{
    private ParameterExpression from;
    private Expression to;
    public ParameterReplaceVisitor(ParameterExpression from, Expression to)
    {
        this.from = from;
        this.to = to;
    }
    protected override Expression VisitParameter(ParameterExpression node)
    {
        return node == from ? to : base.Visit(node);
    }
}

现在您的方法非常简单,因为它除了调用我们的两个方法外几乎什么都不做:

public static Expression<Func<T, bool>> AddStringCompareCriteria<T>(Expression<Func<T, string>> member, string toCompare)
{
    toCompare = toCompare.Trim();

    var start = toCompare[0] == '*';
    var end = toCompare[toCompare.Length - 1] == '*';
    toCompare = toCompare.Trim('*');

    return member.Compose(ComparisonExpression(start, end, toCompare));
}