İki ifadeyi birleştirme (İfade <Func <T, bool >>)


249

İki tür Expression<Func<T, bool>>ifadem var ve bunlardan VEYA VEYA DEĞİL almak ve aynı türden yeni bir ifade almak istiyorum

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

8
Google'dan aldığım çok faydalı yazı: LINQ'dan Varlıklara: Tahminleri Birleştirme
Thomas CG de Vilhena

Yanıtlar:


331

Mantıksal ifadeleri birleştirmek için Expression.AndAlso/ OrElseetc komutunu kullanabilirsiniz , ancak sorun parametrelerdir; ParameterExpressionexpr1 ve expr2'de aynı ile mi çalışıyorsunuz ? Öyleyse, daha kolaydır:

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

Bu aynı zamanda tek bir işlemi reddetmek için de işe yarar:

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

Aksi takdirde, LINQ sağlayıcısına bağlı olarak, bunları aşağıdakilerle birleştirebilirsiniz Invoke:

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

Bir yerde, ihtiyacını kaldırmak için düğümleri değiştirerek bir ifade ağacı yeniden yazan bir kod var Invoke, ama oldukça uzun (ve nerede bıraktığımı hatırlayamıyorum ...)


En basit rotayı seçen genelleştirilmiş sürüm:

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

.NET 4.0'dan başlayarak ExpressionVisitor, EF güvenli ifadeler oluşturmanıza izin veren sınıf vardır.

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

Hey Marc, yukarıdaki ilk kod bloğunda ilk öneriyi denedim, ancak "lambda" ifadesini ilettiğimde <func <T, bool >> bir Where yöntemiyle sonuçlanırken, parametrenin kapsam dışında? Herhangi bir fikir? şerefe
andy

1
+1 genelleştirilmiş sürümü bir cazibe gibi çalışır, kullandım Ve andalso yerine, linq to sql andalso desteklemiyor düşündüm?
Maslow

2
@Maslow - Kaydetmek için ağaçları sıraya koyabilecek bir yeniden yazar: Invoke: stackoverflow.com/questions/1717444/…
Marc Gravell

1
@Aron şimdi tarihe bakın: .NET framework ziyaretçi ( ExpressionVisitor) o zaman yoktu ; Ben ziyaretçi elle uygular benzer bir tarihten stackoverflow ilgili bir örnek var: bir sürü kod.
Marc Gravell

1
@MarkGravell, ifadelerimi birleştirmek için ilk çözümünüzü kullanıyorum ve kurumsal çerçevede bile her şey iyi çalışıyor, Peki son çözümü kullanmanın faydaları ne olurdu?
johnny 5

62

Mantıksal ifadeleri birleştirmek için Expression.AndAlso / OrElse komutunu kullanabilirsiniz, ancak ParameterExpressions öğesinin aynı olduğundan emin olmanız gerekir.

EF ve PredicateBuilder ile sorun yaşıyordum, bu yüzden Invoke'a başvurmadan kendim yaptım, bu şekilde kullanabilirim:

var filterC = filterA.And(filterb);

Benim PredicateBuilder için kaynak kodu:

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

Ve lambda'daki parametreleri değiştirmek için yardımcı sınıf:

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

Bu çözüm, x => x.Property == Değerinin arg => arg.Property2 == Değeri ile birleştirilmesini sağlayan tek çözümdü. Büyük sahne, biraz terli ve kafa karıştırıcı ama işe yarıyor bu yüzden şikayet etmeyeceğim. Kudos Adam :-)
VulgarBinary

Bu harika bir çözüm.
Aaron Stainback

Adam, bu SharePoint Client Object modelinin Linq sağlayıcısını kullanarak yaşadığım çok can sıkıcı bir sorunu çözdü - yayınladığınız için teşekkürler.
Christopher McAtackney

Bu benim için çalıştı! Yüklem geliştirici yanı sıra çeşitli çözümler aramıştı ve bu kadar hiçbir şey işe yaramadı. Teşekkür ederim!
tokyo0709

Bu harika bir kod parçası. Kodu ayarlamak, kopyala yapıştırmak için bir yer bulamadım ve hepsi bu :)
Tolga Evcimen

19

Sağlayıcınız Invoke'u desteklemiyorsa ve iki ifadeyi birleştirmeniz gerekiyorsa, ikinci ifadedeki parametreyi ilk ifadedeki parametreyle değiştirmek için bir ExpressionVisitor kullanabilirsiniz.

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

1
Bu, diğer çözümün aynı istisna ile sonuçlandığı benim özel sorunumu çözdü. Teşekkürler.
Shaun Wilson

1
Bu harika bir çözüm.
Aaron Stainback

3

Yeni burada ama hiçbir şey evli bu cevabı ile bu cevap ve biraz bile ne olup bittiğini anlamaya böylece onu tekrar elden:

public static class ExpressionExtensions
{
    public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    {
        ParameterExpression parameter1 = expr1.Parameters[0];
        var visitor = new ReplaceParameterVisitor(expr2.Parameters[0], parameter1);
        var body2WithParam1 = visitor.Visit(expr2.Body);
        return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, body2WithParam1), parameter1);
    }

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

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

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

            return base.VisitParameter(node);
        }
    }
}

Kavramı kavramakta güçlük çekiyordum ve diğer birkaç cevabı eritmeniz benim için tıklamamda yardımcı oldu. Teşekkürler!
Kevin M. Lapio

2

Aynı sonuçları elde etmek için gerekli, ama daha genel bir şey kullanarak (türü bilinmeyen gibi). Marc'ın cevabı sayesinde sonunda neyi başarmaya çalıştığımı anladım:

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

1

PredicateBuilder ve ExpressionVisitorçözümlerine bir iyileştirme daha öneririm . Ben aradım UnifyParametersByNameve benim MIT kütüphanesinde bulabilirsiniz: LinqExprHelper . Arbitary lambda ifadelerinin birleştirilmesine izin verir. Genellikle yüklem ifadesi hakkında sorular sorulur, ancak bu fikir projeksiyon ifadelerine de uzanır.

Aşağıdaki kod ExprAdres, satır içi lambda kullanarak karmaşık parametrelerle ifade edilen bir yöntem kullanır . Bu karmaşık ifade sadece bir kez kodlanır ve daha sonra LinqExprHelpermini kütüphane sayesinde tekrar kullanılır .

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

Ve bu alt ifade oluşturma kodu:

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

Elde etmeye çalıştığım, kopyalayıp yapıştırmaya gerek kalmadan ve çok güzel olan satır içi lambdaları kullanma yeteneği ile parametreli sorgular yapmaktı. Tüm bu yardımcı ifade olmadan, tek seferde tüm sorguyu oluşturmak zorunda kalırdım.


-7

Bence bu iyi çalışıyor, değil mi?

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

1
bu Linq to SQL için kullanılamaz
Romain Vergnory
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.