Frage

Ich habe zwei Ausdrücke vom Typ Expression<Func<T, bool>> und ich will oder nehmen, und oder nicht von diesen und einen neuen Ausdruck des gleichen Typs erhalten

Expression<Func<T, bool>> expr1;
Expression<Func<T, bool>> expr2;

...

//how to do this (the code below will obviously not work)
Expression<Func<T, bool>> andExpression = expr AND expr2
War es hilfreich?

Lösung

Nun können Sie Expression.AndAlso / OrElse usw. zu kombinieren logische Ausdrücke verwenden, aber das Problem ist, die Parameter; arbeiten Sie mit dem gleichen ParameterExpression in expr1 und expr2? Wenn ja, ist es einfacher:

var body = Expression.AndAlso(expr1.Body, expr2.Body);
var lambda = Expression.Lambda<Func<T,bool>>(body, expr1.Parameters[0]);

Das funktioniert auch gut einen einzigen Vorgang zu negieren:

static Expression<Func<T, bool>> Not<T>(
    this Expression<Func<T, bool>> expr)
{
    return Expression.Lambda<Func<T, bool>>(
        Expression.Not(expr.Body), expr.Parameters[0]);
}

Ansonsten auf den LINQ-Provider abhängig, könnten Sie in der Lage sein, sie mit Invoke zu kombinieren:

// OrElse is very similar...
static Expression<Func<T, bool>> AndAlso<T>(
    this Expression<Func<T, bool>> left,
    Expression<Func<T, bool>> right)
{
    var param = Expression.Parameter(typeof(T), "x");
    var body = Expression.AndAlso(
            Expression.Invoke(left, param),
            Expression.Invoke(right, param)
        );
    var lambda = Expression.Lambda<Func<T, bool>>(body, param);
    return lambda;
}

Irgendwo, ich habe einige Code bekam die wieder schreibt einen Ausdruck Baum ersetzt Knoten die Notwendigkeit Invoke zu entfernen, aber es ist ziemlich lang (und ich kann mich nicht erinnern, wo ich es verlassen ...)


verallgemeinerte Version, die die einfachste Route nimmt:

static Expression<Func<T, bool>> AndAlso<T>(
    this Expression<Func<T, bool>> expr1,
    Expression<Func<T, bool>> expr2)
{
    // need to detect whether they use the same
    // parameter instance; if not, they need fixing
    ParameterExpression param = expr1.Parameters[0];
    if (ReferenceEquals(param, expr2.Parameters[0]))
    {
        // simple version
        return Expression.Lambda<Func<T, bool>>(
            Expression.AndAlso(expr1.Body, expr2.Body), param);
    }
    // otherwise, keep expr1 "as is" and invoke expr2
    return Expression.Lambda<Func<T, bool>>(
        Expression.AndAlso(
            expr1.Body,
            Expression.Invoke(expr2, param)), param);
}

Starten von .net 4.0. Es gibt die ExpressionVistor Klasse, die Sie Ausdrücke erstellen können, die EF sicher sind.

    public static Expression<Func<T, bool>> AndAlso<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    {
        var parameter = Expression.Parameter(typeof (T));

        var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
        var left = leftVisitor.Visit(expr1.Body);

        var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
        var right = rightVisitor.Visit(expr2.Body);

        return Expression.Lambda<Func<T, bool>>(
            Expression.AndAlso(left, right), parameter);
    }



    private class ReplaceExpressionVisitor
        : ExpressionVisitor
    {
        private readonly Expression _oldValue;
        private readonly Expression _newValue;

        public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
        {
            _oldValue = oldValue;
            _newValue = newValue;
        }

        public override Expression Visit(Expression node)
        {
            if (node == _oldValue)
                return _newValue;
            return base.Visit(node);
        }
    }

Andere Tipps

Sie können Expression.AndAlso / OrElse verwenden logische Ausdrücke zu kombinieren, aber Sie müssen die ParameterExpressions gleich sind sicher.

Ich hatte Probleme mit EF und dem PredicateBuilder so machte ich meine eigenen ohne Invoke zurückgreifen, dass ich verwenden könnte wie folgt aus:

var filterC = filterA.And(filterb);

Der Quellcode für meinen PredicateBuilder:

public static class PredicateBuilder {

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {    

        ParameterExpression p = a.Parameters[0];

        SubstExpressionVisitor visitor = new SubstExpressionVisitor();
        visitor.subst[b.Parameters[0]] = p;

        Expression body = Expression.AndAlso(a.Body, visitor.Visit(b.Body));
        return Expression.Lambda<Func<T, bool>>(body, p);
    }

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {    

        ParameterExpression p = a.Parameters[0];

        SubstExpressionVisitor visitor = new SubstExpressionVisitor();
        visitor.subst[b.Parameters[0]] = p;

        Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
        return Expression.Lambda<Func<T, bool>>(body, p);
    }   
}

Und die Utility-Klasse, die Parameter in einem Lambda zu ersetzen:

internal class SubstExpressionVisitor : System.Linq.Expressions.ExpressionVisitor {
        public Dictionary<Expression, Expression> subst = new Dictionary<Expression, Expression>();

        protected override Expression VisitParameter(ParameterExpression node) {
            Expression newValue;
            if (subst.TryGetValue(node, out newValue)) {
                return newValue;
            }
            return node;
        }
    }

Joe Albahari (Autor von C # 3.0 in a Nutshell und LINQPad) schrieb ein Dienstprogramm PredicateBuilder genannt, die AND und Funktionen verwendet werden können, oder zusammen.

http://www.albahari.com/nutshell/predicatebuilder.aspx

Während es auf Funktionen arbeitet, ist es Open Source, so dass Sie es heraus überprüfen und sehen, wie es funktioniert.

Wenn Sie Provider unterstützt Invoke nicht und Sie müssen zwei Ausdruck kombinieren, erhalten Sie einen Expression verwenden können die Parameter in dem zweiten Ausdruck durch den Parameter in dem ersten Ausdruck zu ersetzen.

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

static Expression<Func<T, bool>> UpdateParameter<T>(
    Expression<Func<T, bool>> expr,
    ParameterExpression newParameter)
{
    var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter);
    var body = visitor.Visit(expr.Body);

    return Expression.Lambda<Func<T, bool>>(body, newParameter);
}

[TestMethod]
public void ExpressionText()
{
    string text = "test";

    Expression<Func<Coco, bool>> expr1 = p => p.Item1.Contains(text);
    Expression<Func<Coco, bool>> expr2 = q => q.Item2.Contains(text);
    Expression<Func<Coco, bool>> expr3 = UpdateParameter(expr2, expr1.Parameters[0]);

    var expr4 = Expression.Lambda<Func<Recording, bool>>(
        Expression.OrElse(expr1.Body, expr3.Body), expr1.Parameters[0]);

    var func = expr4.Compile();

    Assert.IsTrue(func(new Coco { Item1 = "caca", Item2 = "test pipi" }));
}

Ich schlage vor, eine weitere Verbesserung zu PredicateBuilder und ExpressionVisitor Lösungen. Ich nannte es UnifyParametersByName und Sie es in MIT-Bibliothek von mir finden: LinqExprHelper . Es ermöglicht die willkürliche Lambda-Ausdrücke zu kombinieren. Normalerweise sind die Fragen über Prädikat Ausdruck gefragt, aber diese Idee erstreckt sich auf Projektions Ausdrücke als auch.

Der folgende Code verwendet ein Verfahren ExprAdres die einen komplizierten parametrisierte Ausdruck schafft, Inline-Lambda verwenden. Dieser komplizierte Ausdruck wird nur einmal codiert, und dann wiederverwendet werden, dank der LinqExprHelper Mini-Bibliothek.

public IQueryable<UbezpExt> UbezpFull
{
    get
    {
        System.Linq.Expressions.Expression<
            Func<UBEZPIECZONY, UBEZP_ADRES, UBEZP_ADRES, UbezpExt>> expr =
            (u, parAdrM, parAdrZ) => new UbezpExt
            {
                Ub = u,
                AdrM = parAdrM,
                AdrZ = parAdrZ,
            };

        // From here an expression builder ExprAdres is called.
        var expr2 = expr
            .ReplacePar("parAdrM", ExprAdres("M").Body)
            .ReplacePar("parAdrZ", ExprAdres("Z").Body);
        return UBEZPIECZONY.Select((Expression<Func<UBEZPIECZONY, UbezpExt>>)expr2);
    }
}

Und das ist die subexpression Bauordnung:

public static Expression<Func<UBEZPIECZONY, UBEZP_ADRES>> ExprAdres(string sTyp)
{
    return u => u.UBEZP_ADRES.Where(a => a.TYP_ADRESU == sTyp)
        .OrderByDescending(a => a.DATAOD).FirstOrDefault();
}

Was ich versuchte zu erreichen war parametrisierte Abfragen ohne Notwendigkeit durchzuführen, um copy-paste und mit der Möglichkeit, Inline-Lambda-Ausdrücke zu verwenden, die so schön ist. Ohne all diese Helfer-expression Sachen, würde ich ganze Abfrage in einem Rutsch zu schaffen gezwungen werden.

I benötigt, um die gleichen Ergebnisse zu erzielen, aber mit etwas mehr Generika (wie der Typ nicht bekannt war). Dank Marcs Antwort, die ich endlich herausgefunden, was ich erreichen wollte:

    public static LambdaExpression CombineOr(Type sourceType, LambdaExpression exp, LambdaExpression newExp) 
    {
        var parameter = Expression.Parameter(sourceType);

        var leftVisitor = new ReplaceExpressionVisitor(exp.Parameters[0], parameter);
        var left = leftVisitor.Visit(exp.Body);

        var rightVisitor = new ReplaceExpressionVisitor(newExp.Parameters[0], parameter);
        var right = rightVisitor.Visit(newExp.Body);

        var delegateType = typeof(Func<,>).MakeGenericType(sourceType, typeof(bool));
        return Expression.Lambda(delegateType, Expression.Or(left, right), parameter);
    }

Ich denke, das funktioniert gut, nicht wahr?

Func<T, bool> expr1 = (x => x.Att1 == "a");
Func<T, bool> expr2 = (x => x.Att2 == "b");
Func<T, bool> expr1ANDexpr2 = (x => expr1(x) && expr2(x));
Func<T, bool> expr1ORexpr2 = (x => expr1(x) || expr2(x));
Func<T, bool> NOTexpr1 = (x => !expr1(x));
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top