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?
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.