Search code examples
c#linqexpression-trees

LINQ Dynamic Query using Expression Tree


I am trying to familiarize myself with Expression Trees, and I'm hitting a wall. I want to be able to dynamically create LINQ to XML queries, so I am trying to familiarize myself with Expression Trees. I started with a simple LINQ to XML statement that I want to be able to generate dynamically:

        // sample data
        var sampleData = new XElement("Items",
            new XElement("Item", new XAttribute("ID", 1)),
            new XElement("Item", new XAttribute("ID", 2)),
            new XElement("Item", new XAttribute("ID", 3))
            );

        // simple example using LINQ to XML (hard-coded)
        var resultsStatic = from item in sampleData.Elements("Item")
                            where item.Attribute("ID").Value == "2"
                            select item;

        // trying to recreate the above dynamically using expression trees
        IQueryable<XElement> queryableData = sampleData.Elements("Item").AsQueryable<XElement>();
        ParameterExpression alias = Expression.Parameter(typeof(XElement), "item");
        MethodInfo attributeMethod = typeof(XElement).GetMethod("Attribute", new Type[] { typeof(XName) });
        PropertyInfo valueProperty = typeof(XAttribute).GetProperty("Value");
        ParameterExpression attributeParam = Expression.Parameter(typeof(XName), "ID");
        Expression methodCall = Expression.Call(alias, attributeMethod, new Expression[] { attributeParam });
        Expression propertyAccessor = Expression.Property(methodCall, valueProperty);
        Expression right = Expression.Constant("2");
        Expression equalityComparison = Expression.Equal(propertyAccessor, right);
        var resultsDynamic = queryableData.Provider.CreateQuery(equalityComparison);

The error that I get when calling CreateQuery is 'Argument expression is not valid'. The debug view for equalityComparison shows '(.Call $item.Attribute($ID)).Value == "2"'. Can someone identify what I am doing incorrectly?


Solution

  • To better understand what's going on, always start with method syntax of the desired query. In your case it is as follows (I'm specifically including the types although normally I would use var):

    IQueryable<XElement> queryableData = sampleData.Elements("Item").AsQueryable();
    IQueryable<XElement> queryStatic = queryableData
        .Where((XElement item) => item.Attribute("ID").Value == "2");
    

    Now let see what you have.

    First, the attributeParam variable

    ParameterExpression attributeParam = Expression.Parameter(typeof(XName), "ID");
    

    As you can see from the static query, there is no lambda parameter for attribute name - the only supported (and required) parameter is the item (which in your code is represented by the alias variable). So this should be ConstantExpression of type XName with value "ID":

    var attributeParam = Expression.Constant((XName)"ID");
    

    Second, the equalityComparison variable. All it contains is the item.Attribute("ID").Value == "2" expression. But Where method requires Expression<Func<XElement, bool>>, so you have to create such using the equalityComparison as body and alias as parameter:

    var predicate = Expression.Lambda<Func<XElement, bool>>(equalityComparison, alias);
    

    Finally you have to call Where method. You can do that directly:

    var queryDynamic = queryableData.Where(predicate);
    

    or dynamically:

    var whereCall = Expression.Call(
        typeof(Queryable), "Where", new Type[] { queryableData.ElementType },
        queryableData.Expression, Expression.Quote(predicate));
    var queryDynamic = queryableData.Provider.CreateQuery(whereCall);
    

    You can take a look at the used Expression methods documentation for further details what they do.