Building Lambda Expressions at Runtime

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.

```csharp 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.

```csharp 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.

```csharp IQueryable devices dataContext.Devices; ```

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

```csharp 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.

```csharp 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

```csharp 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

```csharp delegate BinaryExpression OperatorDelegate( Expression left, Expression right ); ```

And then mapped the users selections to the actual operators.

```csharp 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.

```csharp 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

```csharp 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 results = devices.Provider.CreateQuery(whereCall); ```

Awesome!

~Eoin Campbell

Eoin Campbell

Eoin Campbell
Dad, Husband, Coder, Architect, Nerd, Runner, Photographer, Gamer. I work primarily on the Microsoft .NET & Azure Stack for ChannelSight

CPU Spikes in Azure App Services

Working with Azure App Services and plans which have different CPU utilization profiles Continue reading

Building BuyIrish.com

Published on November 05, 2020

Data Partitioning Strategy in Cosmos DB

Published on June 05, 2018