Dynamic

Building Lambda Expressions at Runtime

Dynamic
Dynamic

Necessity is the mother of all… reasons to learn something new. So when some project requirements came down to put together a Search UI for an object graph of ~200 different properties in one wide table, we got an opportunity to play with some dynamic LINQ. We needed to come up with a quick way to allow a user to search across all the properties without making the UI unwieldy. What we provided them with was a simple UI allowing the user to apply 0:N conjunctive search filters. For each filter they choose an object property to filter by, the filtering operator (equal, less than, etc…) and the value they were searching for.

By the way, if there’s a nicer way to do this, I’d love to know about it.

To get our list of properties they could choose from was relatively simple… Since we already had a Linq2SQL mapping of our Device table, we could just call the GetProperties method on that entity and stick the resultant collection in a ComboBox.

 Dictionary<string, string> returned = typeof(Device).GetProperties();

But how could we use these string representation of object properties in a dynamic fashion. First off, lets have a look at what LINQ is actually doing. Lets take a very simple example.

 var results = dataContext.Devices.Where(
   device => device.DeviceManufacturerId == 123
);

It doesn’t really lend itself nicely to dynamically altering the where clause at runtime. Lucky for us, the query above is really just syntactic sugar and the System.Linq namespace provides us with everything we need to concoct our own lambdas at runtime.

Query Preparation

We want to return a queryable collection of Device Objects. Since the lambda execution is delayed until we attempt to interact with the results, we can safely build up our query in successive steps.

IQueryable<Device> devices dataContext.Devices;

Next we created a ParameterExpression to reference our properties in our Dynamic Lambda.

ParameterExpression parameterExpression = Expression.Parameter(
   typeof(Device), 
   "device"
);

This is equivalent to the “device =>” portion of the query.

We also added our good old “WHERE 1 = 1” hack. Since our UI allowed a user to search without applying any filter, we didn’t want to have to worry about testing for a pre-existing clause before adding the next conjunction predicate.

Expression whereClauseExpression = Expression.Equal(
   Expression.Constant(1), 
   Expression.Constant(1)
);

Right now our query looks like this and any user generated filters can be easily AND’d on

var results = dataContext.Devices.Where(
   device => 1 == 1
);

The Operators

Since the operations that the user was going to use in our UI were well defined, we opted to simplify this part of the system.
numbers: ==, <, >, <=, >=, !=
strings: ==, contains
bools: ==, !=

We created a delegate to represent an Expression Operator

delegate BinaryExpression OperatorDelegate(
   Expression left, 
   Expression right
);

And then mapped the users selections to the actual operators.

Dictionary ops = new Dictionary();
ops.Add("==", Expression.Equal);
//etc...

The dynamagic bit

In order to build a where clause you need 3 things. The property your testing against. The value your testing for. And the operator your applying to your test. We already have our list of known properties from earlier in our combo box. We can also easily find out the type of those properties and hence limit the operations we want the user to be able to use. Finally depending on the property type, we can choose to update the UI and allow the user to type a value in a box for a string/int, or select a radiobutton for a bool etc…

once we’ve obtained these 3 pieces of info, we can build our clause as follows.

string _prop = "DeviceManufacturerId";    //obtained from combobox
string _value = "123";        //int value obtained through textbox
string _operator = "=="; //taken from user selected operator on UI

public static Expression GetNextExpression(ParameterExpression pe,
    string _prop, string _value, string _operator)
{
   Expression left, right;
   OperatorDelegate operatorMethd;

   Type typeOfPropery = typeof(Device) //Get Prop Type
      .GetProperty(_prop)
      .PropertyType;

   TypeConverter conv = TypeDescriptor
      .GetConverter(typeOfPropery);

   //Convert our input string to the same type as our Property
   object o = conv.ConvertFrom(_value); 

   //Left of expression is our property
   left = Expression.MakeMemberAccess(
      parameterExpression,
      typeof(Device).GetProperty(_prop)
   );
   
   //Right side is out type
   right = Expression.Constant(o, typeOfPropery);
   operatorMethd =  ops[_operator];
   return operatorMethod(left, right);
}

This is all equivalent to “device.DeviceManufacturerId == 123” portion of original query

Putting it all together

Finally, now that we have our expression, we can iterate through the rest of the user generated search filters and join them together into one clause.
and execute our lambda

foreach(var in in SomeCollectionOfUserFilters)
{
   //Conjoin existing + next expressions
   whereClauseExpression = Expression.And(
      whereClauseExpression, 
      GetNextExpression(parameterExpression ... )
   );
}

//Generate our Method Call Expression for the Lambda
MethodCallExpression whereCall = Expression.Call(
   typeof(Queryable),
   "Where",
   new Type[] { devices.ElementType },
   devices.Expression,
   Expression.Lambda<Func<Device, bool>>(
      whereClauseExpression, 
      parameterExpression
   )
);

IQueryable<Device> results = devices.Provider.CreateQuery<Device>(whereCall);

Awesome!

~Eoin Campbell

One comment on “Building Lambda Expressions at Runtime

Leave a Reply

Your email address will not be published. Required fields are marked *