6

I want to dynamically build a LINQ query so I can do something like

var list = n.Elements().Where(getQuery("a", "b"));

instead of

var list = n.Elements().Where(e => e.Name = new "a" || e.Name == "c");

(Most of the time, I need to pass XNames with namespaces, not just localnames...)

My problem is in accessing the array elements:

private static Func<XElement, bool> getQuery(XName[] names)
{
    var param = Expression.Parameter(typeof(XElement), "e");

    Expression exp = Expression.Constant(false);
    for (int i = 0; i < names.Length; i++)
    {
         Expression eq = Expression.Equal(
         Expression.Property(param, typeof(XElement).GetProperty("Name")!.Name),
         /*--->*/ Expression.Variable(names[i].GetType(), "names[i]")
         );
    }
    var lambda = Expression.Lambda<Func<XElement, bool>>(exp, param);

    return lambda.Compile();
}

Obviously the Variable expression is wrong, but I'm having difficulty building an expression capable of accessing the array values.

4
  • At the moment I can not test this but I think you could use Expression.Constant of the value instead of Expression.Variable. The constant value can be obtained by using PropertyInfo.GetValue. Commented Dec 17, 2021 at 22:32
  • Why are you using an Expression tree? Compile is pretty expensive... Commented Dec 18, 2021 at 1:03
  • I can only assume that the Expression tree is needed for filtering an IQueryable. In this case Compile woud not be needed. Commented Dec 18, 2021 at 9:31
  • @Clemens - I tried using Constant and various other things but always wind up with an error like "'name' not found" so I'm sure I just don't understand how to build expressions. Commented Dec 20, 2021 at 12:04

2 Answers 2

1

Do you need to create an expression and compile it? Unless I'm missing some nuance to this, all you need is a function that returns a Func<XElement, bool>.

private Func<XElement, bool> GetQuery(params string[] names)
{
    return element => names.Any(n => element.Name == n);
}

This takes an array of strings and returns a Func<XElement>. That function returns true if the element name matches any of the arguments.

You can then use that as you described:

var list = n.Elements.Where(GetQuery("a", "b"));

There are plenty of ways to do something like this. For increased readability an extension like this might be better:

public static class XElementExtensions
{
    public static IEnumerable<XElement> WhereNamesMatch(
        this IEnumerable<XElement> elements, 
        params string[] names)
    {
        return elements.Where(element => 
            names.Any(n => element.Name == n));
    }
}

Then the code that uses it becomes

var list = n.Elements.WhereNamesMatch("a", "b");

That's especially helpful when we have other filters in our LINQ query. All the Where and other methods can become hard to read. But if we isolate them into their own functions with clear names then the usage is easier to read, and we can re-use the extension in different queries.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks! I totally forgot to look at LINQish solutions and this will work for my immediate requirement. This is just a simple example, so I might need to revisit the Expression stuff later.
Even though this doesn't answer the question 'how do I build an expression from an array', I'm going to mark this as the answer because it does solve my problem without using expressions.
1

If you want to write it as Expression you can do it like so:

public static Expression<Func<Person, bool>> GetQuery(Person[] names)
{
    var parameter = Expression.Parameter(typeof(Person), "e");
    var propertyInfo = typeof(Person).GetProperty("Name");

    var expression = names.Aggregate(
        (Expression)Expression.Constant(false),
        (acc, next) => Expression.MakeBinary(
            ExpressionType.Or,
            acc,
            Expression.Equal(
                Expression.Constant(propertyInfo.GetValue(next)),
                Expression.Property(parameter, propertyInfo))));
    
    return Expression.Lambda<Func<Person, bool>>(expression, parameter);
}

Whether or not you compile the expression is determined by the means you want to achieve. If you want to pass the expression to a query provider (cf. Queryable.Where) and have e.g. the database filter your values, then you may not compile the expression.

If you want to filter a collection in memory, i.e. you enumerate all elements (cf. Enumerable.Where) and apply the predicate to all the elements, then you have to compile the expression. In this case you should probably not use the Expression api as this adds complexity to your code and you are then more vulnerable to runtime errors.

2 Comments

I substituted XElement for Person and XName[] for Person[] but still have to use .Compile() to use this in a Where(). And I get "System.Reflection.TargetException: 'Object does not match target type'. What am I misunderstanding?
@JayBuckman: I added my rationale concerning when to compile an expression. You are right for your example you need to compile the expression

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.