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.
I have been coding since the 80's. Well that is kinda true, since I took Computer Science in College, however, it wasn't until Microsoft created an application called Office that I actually started coding in the real world.
Since 1995, I have been in the IT field and dabbled at creating Network Provider business to hook into the internet. At the time T1 lines were the bomb and very expensive, I had one. My partners created a workshop to introduce Windows 95, by selling computers with the operating system already installed. We would house classes of internet browsing and how to use Windows. My partners and I were initially involved in supporting the beta Windows 95 so we were well qualified to instruct on that subject.
I researched and developed all of the business web pages and web applications. My knowledge of VB helped me rise into the ranks of Contractor where I would then learn Java, HTML, and more.
I have since built thousands of web pages and applications, joined the .NET band wagon (though now I am having PHP fun). I currently support SOA Architecture for a marketing firm. I have had lots of fun with .NET. I have recently created websites for my company using MVC and Web API. I like to blog about my experiences when I have a major roadblock and my blogs usually wind up here.