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.
public class User { public int ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
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"} }; }
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.
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.
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
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”.
Note that the following cases were not handled in the above code.
The source code of the above example snippets and demo in the dotnetfiddle is available on GitLab.
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.
Karthik is a passionate Full Stack developer working primarily on .NET Core, microservices, distributed systems, VUE and JavaScript. He also loves NBA basketball so you might find some NBA examples in his posts and he owns this blog.
In this post, we’ll see how to test gRPC Server applications using different clients. And… Read More
In this post, we'll create a new gRPC project in ASP.NET Core and see what's… Read More
In this blog post, we’ll see how to run dotnet core projects without opening visual… Read More
Programmatically evaluating policies is useful when we want to provide access or hide some data… Read More
We saw how we could set up policy-based authorization in our previous article. In this… Read More
What is policy-based authorization and how to set up policy-based authorization with handlers and policies… Read More
This website uses cookies.