Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

The Workings of Dynamic Lambda in LINQ

5.00/5 (3 votes)
7 Sep 2023CPOL4 min read 37.4K  
A quick lesson in LINQ and dynamic Lambda statements
The post discusses the use of LINQ and dynamic lambda statements to create flexible filtering for a collection of objects based on property values entered in text boxes, providing an alternative to static case statements for filtering.

Introduction

In my previous post, Adding Filtering to GridViews, I was challenged with learning how to create dynamic lambda statements for LINQ.

In this post, I am really jotting down some information about what I learned and hope to provide a quick lesson in LINQ and dynamic lambda statements.

First, we will start off by looking at a lambda statement. Say that we want to enumerate through a collection and get the first name that contains a string. Simple enough, right? The below example is a simple pseudo code to find the customers that have a string value in the first name:

C#
IEnumerable<Customer> customer = (from custs in Customers
    where custs.FirstName.Contains(lookupstring)
    select custs);

In a lambda statement, this would now look like the following:

C#
IEnumerable<Customer> customers = Customers
    .Where(customer => customer.FirstName.Contains(LookupString))
    .Select(c => c);

As you can see, the lambda statement uses a parameter called customer for the lookup. The Where statement is looking at all customer objects in Customers where FirstName contains the LookupString. Lambda can be a bit intimidating outside of this simple example.

So now, let's provide a case where we know that the Customer properties will be passed in with a lookup string for that property. We could create a switch case for all of the property names of the Customer and in each case, have the property value Contain the LookupString value.

C#
switch(PropertyName)
{
  case "FirstName":
    IEnumerable<Customer> customers = 
       Customers.Where(customer => 
       customer.FirstName.Contains(LookupValue)).Select(c=>c);
    break;
  case "LastName":
    IEnumerable<Customer> customers = 
      Customers.Wherer(customer => 
      customer.LastName.Contains(LookupValue)).Select(c=>c);
    break;
}

Our problem in this case is that when we add more properties, there has to be a case setup for the filtering. There is also a problem with the above where if we want to filter on FirstName and LastName, there would not be a fun or easy way to do that.

What would be great in that case is to have a dynamic lambda statement created and we filter the list based on that statement. LINQ does provide a method to create a dynamic lambda statement. The System.Linq.Expressions assembly allows us to create parameters for our statement and then place in the Where.

The first thing that needs to be established is the ParameterExpression. This will identify the type and alias of the .Where(customer => customer.FirstName.Contains(LookupValue)) portion of the lambda.

C#
ParameterExpression parameter = Expression.Parameter(typeof(Customer), "customer");

This creates a Customer type and labels it “customer”. The lambda will be able to bind the value to the SQL statement. This is the first step, now we need to call a method. What?? I heard you ask… The string.Contains is a vital part of our requirement. You mean we can invoke a Method from the Expression class? Yes.. we can… Microsoft allows us to map anonymous methods to our lambda so that we can call the Contains method. We have to delve into the Reflection assembly to take advantage of this. Add System.Reflection to the project and create a System.Reflection.MethodInfo object.

C#
MethodInfo containsmethod = 
    typeof(String).GetMethod("Contains", new Type[] { typeof(String) });

We just created a MethodInfo for the String.Contains(string) object. This method returns a boolean if the string value is in the string. Now we have the method to add to our lambda.

We can now deal with the property of the object that we are using. FirstName and LastName are strings so we know that they will have the Contains method. We need to place the LookupValue for the PropertyName in a collection or something. So say, for example, we have several text boxes and as we submit the form, we collect all of the text boxes that have a value, and by property we add them to a IDictionary<string, > with the key being the property name and the value the lookup value. The most popular example of this is the Request.Forms Dictionary in .NET web pages. So for each key in the Dictionary, we can get the property and create a PropertyInfo object from the System.Reflection assembly.

C#
foreach (string propertyname in Request.Form.Keys)
{
  PropertyInfo property = typeof(Consumer).GetProperty(propertyname);
  if(null != property)
    {
      //.... do the work.
    }
}

I put a wrapper around the property if it is null, in case we do something that does not agree with the object we are looking at. We will now add the Contains to the property. Looking at this code block again, let's replace the “//… do the work” with the fun stuff. We need to have an Expression outside of the loop so that we can add to it and use it after the loop, and the same for the MethodCallExpression. We set the MemberAccess of the property so that our parameter can find the property and then we use it in the Expression.Call method. Looping through our Dictionary, we create and add to the existing Expression an “Or” so we get all of our values included in the LINQ.

C#
Expression dynamiclambda = null;
MethodCallExpression call = null;
foreach (string propertyname in Request.Form.Keys)
{
  PropertyInfo property = typeof(Consumer).GetProperty(propertyname);
  if(null != property)
    {
      MemberExpression propertyAccess = 
         Expression.MakeMemberAccess(parameter, property);
      call = Expression.Call(propertyAccess, containsmethod, 
             Expression.Constant(Request.Forms[propertyName]));
      if(null == dynamiclambda)
      {
        dynamiclambda = call;
      }else
      {
        dynamiclambda = Expression.Or(dynamiclambda, call);
      }
    }
}

Now all we have to do is compile our anonymous method and set it to the Where clause. Take a look at the same block of code with the compiling:

C#
Expression dynamiclambda = null;
MethodCallExpression call = null;
foreach (string propertyname in Request.Form.Keys)
{
  PropertyInfo property = typeof(Consumer).GetProperty(propertyname);
  if(null != property)
    {
      MemberExpression propertyAccess = 
          Expression.MakeMemberAccess(parameter, property);
      call = Expression.Call(propertyAccess, containsmethod, 
             Expression.Constant(Request.Forms[propertyName]));
      if(null == dynamiclambda)
      {
        dynamiclambda = call;
      }else
      {
        dynamiclambda = Expression.Or(dynamiclambda, call);
      }
    }
}
if(null != dynamiclambda)
{
  Expression<Func<Customer, bool>> predicate = 
     Expression.Lambda<Func<Customer, bool>>(dynamiclambda, parameter);
  Func<Customer, bool> compiled = predicate.Compile();
  IList<Customer> datasource = Customers.Where(compiled).ToList();
}

So the final IList<customer> is created by our dynamic lambda. Easier than you thought, right? Our anonymous method is of type Customer and returns a bool so it matches our Property.Contains(string) method. We can now find values in the property value. This will replace the need for the case statement and allow us to add more textboxes to our application later without needing to mess with the dynamic code for our lambda.

I recently did this with a GridView control by adding filtering to the columns. Have fun with this tidbit of knowledge.

Comments and questions are welcome.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)