Skip to content

Building dynamic LINQ queries using Expression Trees and Func

Filtering an item from a list of items is easy but if the item to filter is not known at compile time then filtering a list will be little complicated.

Instead of writing dynamic filtering in C#, one might go for a Stored Procedure and write a dynamic query based on the input column name and execute the dynamic SQL statement.

In this post, we will see how to build the dynamic LINQ queries. Building dynamic LINQ expressions is easy but you need to have an idea on Func<T, TResult> and Expression Trees before we begin.

The User class

public class User
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Querying on Seed data

For the purpose of the article, we will have the following list of user data.

private static List<User> UserDataSeed()
{
    return new List<User>
    {
        new User{ ID = 1, FirstName = "Kevin", LastName = "Garnett"},
        new User{ ID = 2, FirstName = "Stephen", LastName = "Curry"},
        new User{ ID = 3, FirstName = "Kevin", LastName = "Durant"}
    };
}

What is a Func delegate?

Encapsulates a method that has one parameter and returns a value of the type specified by the TResult parameter. – from MSDN

Func is a delegate type for a method that returns the value of the type T. You can use Func to reference a method.

For example, if we want to multiply two numbers, then with Func delegate we can write the following code

//Func will take two integers as arguments to multiply
//and will return the output as integers
Func<int, int, int> multiply = (num1, num2) => num1 * num2;
Console.WriteLine(multiply(2, 3));

Back to our original dynamic LINQ, we’ll use Func delegate to have dynamic LINQ queries.

Dynamic Querying with Func

If we want to get the specific ID of the user in a list of users then we will write the following LINQ on the list of users for different properties on the User model.

For filtering the ID property:

userData.Where(d => d.ID == val).ToList();

For FirstName?

userData.Where(d => d.FirstName == val).ToList();

Last name?

userData.Where(d => d.LastName == val).ToList();

From our console app, we will supply a property name to filter on. So, can we have a switch statement to do this? (I know some of you don’t like the switch-case statement).

private static Func<User, bool> GetDynamicQueryWithFunc(string propName, object val)
{
    Func<User, bool> exp = (t) => true;
    switch (propName)
    {
        case "ID":
            exp = d => d.ID == Convert.ToInt32(val);
            break;
        case "FirstName":
            exp = f => f.FirstName == Convert.ToString(val);
            break;
        case "LastName":
            exp = l => l.LastName == Convert.ToString(val);
            break;
        default:
            break;
    }
    return exp;
}

In the above code snippet, we declared a Func delegate (exp) at the top and based on the property name we will assign the necessary lambda expression to the func delegate and return the delegate back to the caller.

Once we get the Func delegate from the method, we will apply that delegate on a list of user data.

var dynamicExpression = GetDynamicQueryWithFunc(propertyName, value);
var output = userData.Where(dynamicExpression).ToList();

I don’t like the above solution, although it solves the problem. Because, the code violates Open-Closed principle and the code use switch-case statement.

Let’s do the real dynamic LINQ querying with Expression trees.

What is an Expression Tree?

An expression tree is a binary tree that will represent expressions. So, we have to build the expressions for the tree.

In C#, the expression trees concept is used to translate code into data.

With expression trees, we produce a data structure that represents your code.

Why do we need the expression trees now? We’ll use the expression trees to build our code as a data structure and we compile the expression tree to get a func delegate which can be used to filter the items.

Please read expresson trees from msdn

Building dynamically with Expression trees

To represent x => x.LastName == "Curry" in expression trees, we have to write the following code.

private static Func<User, bool> GetDynamicQueryWithExpresionTrees(string propertyName, string val)
{
    //x =>
    var param = Expression.Parameter(typeof(User), "x");
    //val ("Curry")
    var valExpression = Expression.Constant(val, typeof(string));
    //x.LastName == "Curry"
    Expression body = Expression.Equal(member, valExpression);
    //x => x.LastName == "Curry"
    var final = Expression.Lambda<Func<User, bool>>(body: body, parameters: param);
    //compiles the expression tree to a func delegate
    return final.Compile();
}

In the above code we have the following line

var valExpression = Expression.Constant(val, typeof(string));

This makes the constant expression of type string but what if we give ID as the input to the program and run it. Well, it breaks with the following exception.

System.InvalidOperationException: ‘The binary operator Equal is not defined for the types ‘System.Int32’ and ‘System.String’.’

To fix this we have to get rid of that line and we will use the TypeDescriptor to convert our value to the appropriate type.

As said, the following code will convert from a string to its own type of data.

var param = Expression.Parameter(typeof(User), "x");
var member = Expression.Property(param, propertyName);
var propertyType = ((PropertyInfo)member.Member).PropertyType;
var converter = TypeDescriptor.GetConverter(propertyType);

if (!converter.CanConvertFrom(typeof(string)))
    throw new NotSupportedException();

//will give the integer value if the string is integer
var propertyValue = converter.ConvertFromInvariantString(val); 
var constant = Expression.Constant(propertyValue);

You can also use int.TryParse to test if the string is an integer. If we chose this approach then we have to use if/else or switch-case statements to make that constant expression and also have to handle the value expression.

With the TypeDescriptor now in place let’s see the entire code.

    static void Main(string[] args)
    {
        Console.WriteLine("Specify the property to filter");
        string propertyName = Console.ReadLine();
        Console.WriteLine("Value to search against: " + propertyName);
        string value = Console.ReadLine();

        //1: With Func delegate
        //var dynamicExpression = GetDynamicQueryWithFunc(propertyName, value);
        //var output = userData.Where(dynamicExpression).ToList();

        //2: With Expression trees that generate Func and handles dynamic types with TypeDescriptor
        var dn = GetDynamicQueryWithExpresionTrees(propertyName, value);
        var output = userData.Where(dn).ToList();

        foreach (var item in output)
        {
            Console.WriteLine("Filtered result:");
            Console.WriteLine($"\t ID: {item.ID}");
            Console.WriteLine($"\t First Name: {item.FirstName}");
            Console.WriteLine($"\t Last Name: {item.LastName}");
        }
    }

    //USER DATA SEED
    private static List<User> UserDataSeed()
    {
        return new List<User>
        {
            new User{ ID = 1, FirstName = "Kevin", LastName = "Garnett"},
            new User{ ID = 2, FirstName = "Stephen", LastName = "Curry"},
            new User{ ID = 3, FirstName = "Kevin", LastName = "Durant"}
        };
    }
    private static Func<User, bool> GetDynamicQueryWithExpresionTrees(string propertyName, string val)
    {
        var param = Expression.Parameter(typeof(User), "x");

        #region Convert to specific data type
        MemberExpression member = Expression.Property(param, propertyName);
        UnaryExpression valueExpression = GetValueExpression(propertyName, val, param);
        #endregion

        Expression body = Expression.Equal(member, valueExpression);
        var final = Expression.Lambda<Func<User, bool>>(body: body, parameters: param);
        return final.Compile();
    }

    private static UnaryExpression GetValueExpression(string propertyName, string val, ParameterExpression param)
    {
        var member = Expression.Property(param, propertyName);
        var propertyType = ((PropertyInfo)member.Member).PropertyType;
        var converter = TypeDescriptor.GetConverter(propertyType);

        if (!converter.CanConvertFrom(typeof(string)))
            throw new NotSupportedException();

        var propertyValue = converter.ConvertFromInvariantString(val);
        var constant = Expression.Constant(propertyValue);
        return Expression.Convert(constant, propertyType);
    }

The above code blocks is a tabbed content check the second tab for the dynamic LINQ generation using Expression Trees.

Let’s try the code in dotnet fiddle and see it in action.

In the output window of the dotnet fiddle, try with ID property with the value within 1, 2, 3 or FirstName/LastName with the names “Kevin”/”Curry”.

Cases not handled in the code

Note that the following cases were not handled in the above code.

  • Giving property names with spaces will cause the program to throw an exception as the property with names(ex: First name) is not in the User model.
  • Providing a value that is not in the user seed data will cause the program to end without providing any result.

Source Code on GitLab

The source code of the above example snippets and demo in the dotnetfiddle is available on GitLab.

Conclusion

The old way of doing things dynamic is to write a stored procedure with a ton of IF statements and build a raw SQL query and then execute it at the end.

Instead of writing the dynamic SQL queries it is better to build the Expression Trees or Func and then use it against the data to filter the result so that we can test the code.

One might argue that we can filter a list of item dynamically with reflection, but reflection will be slow if the model class (in our case the User class) has too many properties.

2 thoughts on “Building dynamic LINQ queries using Expression Trees and Func”

  1. Pingback: Don't use Func delegate on the Entity Framework entities - Code Rethinked

  2. Pingback: Google

Leave a Reply

Your email address will not be published.