Home > Software engineering >  How do I use an array of values in a LINQ Expression builder?
How do I use an array of values in a LINQ Expression builder?

Time:12-21

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.

CodePudding user response:

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.

CodePudding user response:

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