如何在Linq中追加表达式? [英] How to append expressions in linq?

查看:97
本文介绍了如何在Linq中追加表达式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

NET核心应用程序。我实现了一个通用存储库模式。我正在尝试实现一些过滤功能。我有以下代码。

var param = Expression.Parameter(typeof(SiteAssessmentRequest), "x");
Expression<Func<SiteAssessmentRequest, bool>> query;
query = x => x.CreatedBy == request.Userid || x.AssignedTo == request.Userid;
Expression body = Expression.Invoke(query, param);
if (request.Client != null && request.Client.Length != 0)
            {
                Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.Client.Contains(x.Client);
                body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
            }

            if (request.CountryId != null && request.CountryId.Length != 0)
            {
                Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.CountryId.Contains(x.CountryId);
                body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
            }

            if (request.SiteName != null && request.SiteName.Length != 0)
            {
                Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.SiteName.Contains(x.SiteName);
                body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
            }

            if (request.Status != null && request.Status.Length != 0)
            {
                Expression<Func<SiteAssessmentRequest, bool>> internalQuery = x => request.Status.Contains(x.Status);
                body = Expression.AndAlso(Expression.Invoke(query, param), Expression.Invoke(internalQuery, param));
            }
            var lambda = Expression.Lambda<Func<SiteAssessmentRequest, bool>>(body, param);
var siteAssessmentRequest = await _siteAssessmentRequestRepository.GetAsync(lambda, null, x => x.Country).ConfigureAwait(false);

在上面的代码中,当我传递多个参数时,例如请求。Status和Request.SiteName我要根据Status和Sitename进行筛选。当我看到查询时,查询中只附加了一个参数

{x => (Invoke(x => (Not(IsNullOrEmpty(x.CreatedBy)) AndAlso Not(IsNullOrWhiteSpace(x.CreatedBy))), x)
 AndAlso Invoke(x => value(Site.V1.Implementation.GetSARByFilterAr+<>c__DisplayClass12_0)
.request.Status.Contains(x.Status), x))} 

搜索了这么多之后,我得到了下面的代码

public static class ExpressionCombiner
    {
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> exp, Expression<Func<T, bool>> newExp)
        {
            // get the visitor
            var visitor = new ParameterUpdateVisitor(newExp.Parameters.First(), exp.Parameters.First());
            // replace the parameter in the expression just created
            newExp = visitor.Visit(newExp) as Expression<Func<T, bool>>;

            // now you can and together the two expressions
            var binExp = Expression.And(exp.Body, newExp.Body);
            // and return a new lambda, that will do what you want. NOTE that the binExp has reference only to te newExp.Parameters[0] (there is only 1) parameter, and no other
            return Expression.Lambda<Func<T, bool>>(binExp, newExp.Parameters);
        }

        class ParameterUpdateVisitor : ExpressionVisitor
        {
            private ParameterExpression _oldParameter;
            private ParameterExpression _newParameter;

            public ParameterUpdateVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
            {
                _oldParameter = oldParameter;
                _newParameter = newParameter;
            }

            protected override Expression VisitParameter(ParameterExpression node)
            {
                if (object.ReferenceEquals(node, _oldParameter))
                    return _newParameter;

                return base.VisitParameter(node);
            }
        }
    }

但我正在努力使其在代码之上工作。 在上面的查询中,我只看到状态,没有看到站点名称。所以我想包括多个表达式。有谁能帮帮我吗?任何帮助都将不胜感激。谢谢

推荐答案

让我解释一下LambdaExpression的核心概念。LambdaExpression有0..N个参数和正文。LambdaExpression的正文是我们实际上要重复使用的表达式。

基本错误是试图组合lambda表达式(更改参数名称是因为它是ExpressionTree的工作方式-它按引用而不是按名称比较参数):

Expression<Func<Some, bool>> lambda1 = x1 => x1.Id == 10;
Expression<Func<Some, bool>> lambda2 = x2 => x2.Value == 20;
// wrong
var resultExpression = Expression.AndAlso(lambda1, lambda2);

示意性上一个错误的样本应该是这样的(即使它会崩溃)

(x1 => x1.Id == 10) && (x2 => x2.Value == 20)

我们所拥有的-不是所需的表达式,而是";函数和quot;的组合。

让LambdaExpression的正文重用

// not complete
var newBody = Expression.AndAlso(lambda1.Body, lambda2.Body);

结果更可接受,但仍需更正:

(x1.Id == 10) && (x2.Value == 20)

为什么我们需要更正?因为我们正在尝试构建以下LambdaExpression

var param = Expression.Parameter(typeof(some), "e");

var newBody = Expression.AndAlso(lambda1.Body, lambda2.Body);
// still wrong
var newPredicate = Expression.Lambda<Func<Some, bool>>(newBody, param)

newPredicate的结果应为

e => (x1.Id == 10) && (x2.Value == 20)
如您所见,我们将前面两个lambdas中的Body参数保留下来,这是错误的,我们必须用新参数param(&q;e";)替换它们,最好在组合之前这样做。 我正在使用我的replacer实现,它只返回所需的body

那么,让我们编写正确的lambda!

Expression<Func<Some, bool>> lambda1 = x1 => x1.Id == 10;
Expression<Func<Some, bool>> lambda2 = x2 => x2.Value == 20;

var param = Expression.Parameter(typeof(Some), "e");

var newBody = Expression.AndAlso(
  ExpressionReplacer.GetBody(lambda1, param), 
  ExpressionReplacer.GetBody(lambda2, param));

// hurray!
var newPredicate = Expression.Lambda<Func<Some, bool>>(newBody, param);

var query = query.Where(newPredicate);

完成这些步骤后,您将获得所需的结果:

e => (e.Id == 10) && (e.Value == 20)

ExpressionReplacer实施

class ExpressionReplacer : ExpressionVisitor
{
    readonly IDictionary<Expression, Expression> _replaceMap;

    public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
    {
        _replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
    }

    public override Expression Visit(Expression exp)
    {
        if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
            return replacement;
        return base.Visit(exp);
    }

    public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
    {
        return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
    }

    public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
    {
        return new ExpressionReplacer(replaceMap).Visit(expr);
    }

    public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
    {
        if (lambda.Parameters.Count != toReplace.Length)
            throw new InvalidOperationException();

        return new ExpressionReplacer(Enumerable.Zip(lambda.Parameters, toReplace, (f, s) => Tuple.Create(f, s))
            .ToDictionary(e => (Expression)e.Item1, e => e.Item2)).Visit(lambda.Body);
    }
}

如果您计划使用ExpressionTree Closer,我建议安装此VS扩展,它将简化您的生活:Readable Expressions

这篇关于如何在Linq中追加表达式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆