Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Build expressions
class Employee
{
public IList<Order> Orders { get; set; }
}
class Order
{
public int OrderID { get; set; }
public IList<Customer> Customers { get; set; }
}
class Customer
{
public int CustomerID { get; set; }
}
private static Expression BuildAccessors(Expression parent, string[] properties, int index)
{
if (index < properties.Length)
{
var member = properties[index];
// If it's IEnumerable like Orders is, then we need to do something more complicated
if (typeof(IEnumerable).IsAssignableFrom(parent.Type) && parent.Type != typeof(string))
{
var enumerableType = parent.Type.GetGenericArguments().SingleOrDefault(); // input eg: Employee.Orders (type IList<Order>), output: type Order
var param = Expression.Parameter(enumerableType, "x"); // declare parameter for the lambda expression of Orders.Select(x => x.OrderID)
var lambdaBody = BuildAccessors(param, properties, index); // Recurse to build the inside of the lambda, so x => x.OrderID.
var funcType = typeof(Func<,>).MakeGenericType(enumerableType, lambdaBody.Type); // Lambda is of type Func<Order, int> in the case of x => x.OrderID
var lambda = Expression.Lambda(funcType, lambdaBody, param);
// This part is messy, I want to find the method Enumerable.Select<Order, int>(..) but I don't think there's a more succint way. Might be wrong.
var selectMethod = (from m in typeof(Enumerable).GetMethods()
where m.Name == "Select"
&& m.IsGenericMethod
let parameters = m.GetParameters()
where parameters.Length == 2
&& parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>)
select m).Single().MakeGenericMethod(enumerableType, lambdaBody.Type);
// Do Orders.Select(x => x.OrderID)
var invokeSelect = Expression.Call(null, selectMethod, parent, lambda);
return invokeSelect;
}
else
{
// Simply access a property like OrderID
var newParent = Expression.PropertyOrField(parent, member);
// Recurse
return BuildAccessors(newParent, properties, ++index);
}
}
else
{
// Return the final expression once we're done recursing.
return parent;
}
}
static void Main(string[] args)
{
// Slightly more complex example to show it should be able to handle this scenario too
var str = "Employee.Orders.Customers.CustomerID";
var split = str.Split('.');
// Create the root of the expression, namely accessing an employee variable. Could be a Expression.Parameter too.
var baseExpr = Expression.Variable(typeof(Employee), split[0]);
// Start at index 1, we've already processed index 0 (the root)
var result = BuildAccessors(baseExpr, split, 1);
// Create the resulting lambda
var lambda = Expression.Lambda<Func<Employee, IEnumerable<IEnumerable<int>>>>(result, baseExpr);
// Compile to verify that it's valid
var del = lambda.Compile();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.