Funzione di creazione di espressioni per String.Contains e string []

.net c# expression-trees

Domanda

Sto avendo difficoltà a creare una funzione di generazione di espressioni da passare a una query per il repository di NHibernate.

Fondamentalmente quello che voglio essere in grado di fare è costruire una query da varie proprietà dell'oggetto (stringhe) e confrontare il loro valore con i valori in una matrice. Inoltre, puoi aggiungere altre frasi di query.

Qualcosa di simile a

public static Expression<Func<TElement, bool>> BuildOrExpression<TElement, TValue>
    (Dictionary<Expression<Func<TElement, TValue>>, IEnumerable<TValue>> expressionSet)
{
    List<Expression> newExpressions = new List<Expression>();

    foreach (var item in expressionSet)
    {
        if (null == item.Key) throw new ArgumentNullException("valueSelector");
        if (null == item.Value) throw new ArgumentNullException("values");

        ParameterExpression p = item.Key.Parameters.Single();

        if (!item.Value.Any()) return e => false;

        IEnumerable<Expression> expressions = item.Value
           .Select(value => (Expression)Expression.Equal(item.Key.Body, Expression.Constant(value, typeof(TValue))));
        Expression compExpression = expressions
            .Aggregate<Expression>((accumulate, equal) => Expression.OrElse(accumulate, equal));
        newExpressions.Add(Expression.Lambda<Func<TElement, bool>>(compExpression, p));
    }

    Expression accExpression = newExpressions
        .Aggregate<Expression>((accumulate, equal) => Expression.OrElse(accumulate, equal));

    return Expression.Lambda<Func<TElement, bool>>(accExpression);
}

Ma invece di eseguire Expression.Equal qualche modo facendo un String.Contains .

Qualsiasi aiuto accolto. Tra l'altro il codice sopra non è testato.

Risposta accettata

Sono stato in grado di superare l'espressione che combinava il problema, ma non sono sicuro che questa sia la soluzione migliore. Chiunque abbia usato la soluzione fornita da Vladimir e PredicateBuilder da Joe Albahari (scrittore di libri C # 4). Questo è quello che mi è venuto in mente.

    public static Expression<Func<TElement, bool>> BuildOrExpression<TElement, TValue>(Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
    {
        if (null == valueSelector) throw new ArgumentNullException("valueSelector");
        if (null == values) throw new ArgumentNullException("values");

        ParameterExpression p = valueSelector.Parameters.Single();

        if (!values.Any()) return e => false;

        Expression<Func<string, string, bool>> expFunc = (name, value) => name.Contains(value);
        IEnumerable<Expression> equals = values.Select(value => (Expression)Expression.Call(valueSelector.Body, typeof(String).GetMethod("Contains"), Expression.Constant(value, typeof(TValue))));
        Expression body = equals.Aggregate<Expression>((accumulate, equal) => Expression.OrElse(accumulate, equal));

        return Expression.Lambda<Func<TElement, bool>>(body, p);
    }
    public static Expression<Func<T, bool>> CombineOr<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
    }
    public static Expression<Func<T, bool>> CombineAnd<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
    }

    [TestMethod()]
    public void OfferComplexSearchTest()
    {
        using (var lifetime = container.BeginLifetimeScope())
        {
            IOfferRepository offerRepository = lifetime.Resolve<IOfferRepository>();
            List<Offer> offers = new List<Offer>();
            List<Expression<Func<Offer, bool>>> expressions = new List<Expression<Func<Offer, bool>>>();

            Expression<Func<Offer, bool>> finalExp = null;

            string[] orQuery = new string[2] { "director".ToUpper(), "jefe".ToUpper() };

            expressions.Add(LinqTools.BuildOrExpression<Offer, string>(c => c.Description.ToUpper(), orQuery));
            expressions.Add(LinqTools.BuildOrExpression<Offer, string>(c => c.Title.ToUpper(), orQuery));
            expressions.Add(LinqTools.BuildOrExpression<Offer, string>(c => c.Keywords.ToUpper(), orQuery));

            finalExp = expressions.Aggregate<Expression<Func<Offer, bool>>>((accomulate, equal) => LinqTools.CombineOr<Offer>(accomulate, equal));

            offers.AddRange(offerRepository.GetMany(LinqTools.CombineAnd<Offer>(finalExp, (x) => x.Publish)));

            Assert.IsTrue(offers.Count > 0, "Error: No Offers found.");
        }
    }

Risposta popolare

Invece di Expression.Equal è necessario utilizzare Expression.Call e passare l'istanza di MethodInfo Contain. Qualcosa come questo

IEnumerable<Expression> expressions = item.Value.Select(value => 
   (Expression)Expression.Call(item.Key.Body, 
   typeof(String).GetMethod("Contains"), 
   Expression.Constant(value, typeof(TValue))));


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché