0

我有一个表单来搜索条件,我使用 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);
    }
}

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

4

1 回答 1

2

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

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

因此,我们将首先调整您RightMethod的返回值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));
}
于 2019-02-01T14:15:50.037 回答