Frage

First of all I want to make you know that I've searched another questions and answers before making this one, but I cound't find any that would help me in this specific problem I am facing.

I need to filter registers based in two properties of its class, one of them being the field corresponding to the search, and the other one being a numeric code of another entity to which the register must be referenced in the database.

My search function has the following signature:

public List<TView> SearchByField(int parentCode, string fieldName, string filter);

I've tried to implement this using Expression Trees, and got it to get two expressions, but now I didn't get to combine these expressions to build one to pass to the final

Expression.AndAlso(parentCodeFilterExpression, textFilterExpression);

that will combine the too expressions in only one.

What I got so far was the code shown below (sorry for the long snippet, but I think this was necessary to make it easier to understand the question):

public List<TView> SearchPerField(int parentCode, string fieldName, string filter)
    {
        var lambdaExpression = GetLambdaExpressionForSearchByField(fieldName, filter, parentCode);

        return new PersistenciaImpl<TView>().Where(lambdaExpression).ToList();
    }

    private Expression<Func<TView, bool>> GetLambdaExpressionForSearchByField(string fieldName, string filter, int parentCode)
    {
        Expression<Func<TView, bool>> textFilterExpression = GetTextFilterExpression(fieldName, filter);

        Expression<Func<TView, bool>> parentCodeFilterExpression = GetParentCodeFilterExpression(parentCode);

        Expression.Lambda<Func<TView, bool>>(textFilterExpression, parentCodeFilterExpression);

        // THIS IS THE POINT. HOW TO MAKE THIS WORK?
        Expression.AndAlso(parentCodeFilterExpression, textFilterExpression);

        return textFilterExpression;
    }

    private Expression<Func<TView, bool>> GetParentCodeFilterExpression(int parentCode)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(TView), "x");

        Expression parent = Expression.Property(parameter, "Parent");

        Expression parentCodeExpression = Expression.Property(parent, "Code");

        Expression target = Expression.Constant(parentCode);

        Expression containsMethod = Expression.Call(parentCodeExpression, "Equals", null, target);

        Expression<Func<TView, bool>> textFilterExpression =
           Expression.Lambda<Func<TView, bool>>(containsMethod, parameter);

        return textFilterExpression;
    }

    private Expression<Func<TView, bool>> GetTextFilterExpression(string fieldName, string filter)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(TView), "x");

        Expression property = Expression.Property(parameter, fieldName);

        Expression target = Expression.Constant(filter.ToUpper());

        Expression containsMethod = Expression.Call(property, "Contains", null, target);

        Expression<Func<TView, bool>> textFilterExpression =
           Expression.Lambda<Func<TView, bool>>(containsMethod, parameter);

        return textFilterExpression;
    }

Thanks for any suggestion.

War es hilfreich?

Lösung

I've tried to implement this using Expression Trees, and got it to get two expressions, but now I didn't get to combine these expressions to build one to pass to the final

First, you need to declare a parameter for your final (outer) lambda. Then you need to invoke your two filter (inner) lambdas independently, passing in the same argument to each:

// using E = System.Linq.Expressions.Expression;

var item = E.Parameter(typeof(TView));

var combined = E.Lambda<Func<TView, bool>>(
    E.AndAlso(
        E.Invoke(parentCodeFilterExpression, item), 
        E.Invoke(textFilterExpression, item)),
    item);

If you need these expressions to be compatible with a query provider like Entity Framework, things get a bit messier because Invoke expressions probably aren't supported. You'll have to manually inline the two filter lambdas, which requires walking each filter's body and replacing the inner parameter references with references to the outer lambda's parameter:

// using E = System.Linq.Expressions.Expression;

sealed class ParameterReplacementVisitor : ExpressionVisitor
{
    private readonly IDictionary<E, E> _replacements;

    public ParameterReplacementVisitor(IDictionary<E, E> replacements)
    {
        _replacements = replacements;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        E replacement;

        if (_replacements.TryGetValue(node, out replacement))
            return this.Visit(replacement);

        return base.VisitParameter(node);
    }
}

// ...

var item = E.Parameter(typeof(TView));

var visitor = new ParameterReplacementVisitor(
    new Dictionary<E, E> {
        { parentCodeFilterExpression.Parameters[0], item },
        { textFilterExpression.Parameters[0], item }
    }
);

var combined = E.Lambda<Func<TView, bool>>(
    E.AndAlso(
        visitor.Visit(parentCodeFilterExpression.Body), 
        visitor.Visit(textFilterExpression.Body)), 
    item);

Alternatively, if you are composing the inner expressions in a closed environment as your post suggests, you could simply pass the outer lambda parameter as an argument to the methods that construct the inner expressions, and return just the bodies (don't bother wrapping the inner filters in lambdas).

Andere Tipps

I think you need something like this:

MethodCallExpression where = Expression.Call((
            typeof(Queryable), 
            "Where", 
            new Type[] { TView },  
            lambdaExpression );

Please note that I don't consider this a solution; it's merely an idea or example. Maybe this link will help you out.

You can compile an Expression<TDelegate> into a TDelegate by using the Compile method on the Expression:

Expression<Func<TView, bool>> lambdaExpression =
    GetLambdaExpressionForSearchByField(fieldName, filter, parentCode);
Func<TView, bool> func = lambdaExpression.Compile();

Once you have that you can use it as a parameter for the Where function.


With the code above you can then use

return new PersistenciaImpl<TView>().Where(func).ToList();
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top