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:
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:
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.
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.
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.
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 string
s 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.
foreach (string propertyname in Request.Form.Keys)
{
PropertyInfo property = typeof(Consumer).GetProperty(propertyname);
if(null != property)
{
}
}
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.
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:
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.