参数在复杂对象时替换

时间:2014-09-08 15:11:22

标签: c# linq expression-trees expressionvisitor

我正在尝试创建一个动态AndAlso过滤器,该过滤器将在Where方法中用于LINQ-to-EF查询:

query.Where(filterExpression)

其中filterExpression是已编译的lambda

到目前为止,我已经实现了一个带有一些奇特用法的循环来检查只有一个,两个或更多查询的情况。

Expression selectLeft = null;
Expression selectRight = null;
Expression filterExpression = null;

foreach (QueryColumn columnQuery in querycolumns)
{
    Expression<Func<FileColumnRecords, bool>> 
                columnPredicate = d => d.fcv.Any(f => (f.value != null ? 
                                  f.value.ToLower().Contains(columnQuery.queryTerm.ToLower()) :
                                  false));

    if (selectLeft == null)
    {
        selectLeft = columnPredicate.Body;
        filterExpression = selectLeft;
        continue;
    }
    if (selectRight == null)
    {
        selectRight = columnPredicate.Body;
        filterExpression =
        Expression.AndAlso(selectLeft, selectRight);
        continue;
    }

    filterExpression =
            Expression.AndAlso(filterExpression, columnPredicate.Body);
}

然后我设置ParameterReplacer以确保我的表达式参数的所有迭代都得到相同的引用:

ParameterExpression param = Expression.Parameter(typeof(FileColumnRecords), "p");
ParameterReplacer replacer = new ParameterReplacer(param);
filterExpression = replacer.Visit(filterExpression);

建立于:

class ParameterReplacer : ExpressionVisitor
{
    private readonly ParameterExpression parameter;

    internal ParameterReplacer(ParameterExpression parameter)
    {
        this.parameter = parameter;
    }

    protected override Expression VisitParameter
        (ParameterExpression node)
    {
        return parameter;
    }
}

ParameterReplacer课程由JonSkeet提供)

运行ParameterReplacer时出现以下错误:

"Property 'System.String value' is not defined for type 'Models.FileColumnRecords'"

其中FileColumnRecords定义为:

public class FileColumnRecords
{       
    public Documents doc;

    public IEnumerable<FileColumnValues> fcv;
}

FileColumnValues as:

public partial class FileColumnValues
{
    public long ID { get; set; }
    public long CNID { get; set; }
    public long fileID { get; set; }
    public string value { get; set; }
}

2 个答案:

答案 0 :(得分:3)

我怀疑,由于表达式树中嵌入了参数,您遇到了困难。替换者应该只替换顶级参数。

您可以将顶级表达式传递给ParameterReplacer

class ParameterReplacer : ExpressionVisitor
{
    private readonly ParameterExpression target;
    private readonly ISet<ParameterExpression> sources;

    internal ParameterReplacer(ParameterExpression target,
                               IEnumerable<LambdaExpression> expressions)
    {
        this.target = target;
        sources = new HashSet<ParameterExpression>(
            expressions.SelectMany(e => e.Parameters));
    }

    protected override Expression VisitParameter
        (ParameterExpression node)
    {
        return sources.Contains(node) ? target : node;
    }
}

然后,您需要更改代码的其余部分,以构建您已合并的表达式树列表。肯定有更优雅的方式,但我认为这应该有效。但是,这只是我的头脑 - 我还没有尝试过。

说完这一切之后,您确定不能只使用PredicateBuilder吗?

答案 1 :(得分:0)

根据JonSkeet Answer的帖子,使用了PredicateBuilder JonSkeet在LINQKit库中提出的一个惊人的方法{{3}},我想出了以下简单的解决方案:

public static IQueryable<FileColumnRecords> DynamicColumnQuery
                                (this IQueryable<FileColumnRecords> query, 
                                List<QueryColumn> querycolumns)
{
    var predicate = PredicateBuilder.False<FileColumnRecords>();
    foreach (QueryColumn columnQuery in querycolumns)
    {
        string temp = columnQuery.queryTerm.ToLower();
        predicate = predicate.Or(d => d.fcv.Any(f => (f.value != null ?
                                 f.value.ToLower().Contains(temp) : false));
    }

    return query.AsExpandable().Where(predicate);
}

如果你对编写表达式树有一点了解,那么你就会立刻意识到这是多么令人难以置信的 AWESOME 。另外,请注意我在谓词中嵌入参数的方式。

  

我与LINQKit的开发者,Joseph或Ben Albahari没有任何关系   也不是O&Reilly出版商。只是一个非常满意的开发人员认为我应该   分享他们所建造的精彩内容。