Use Expression Trees to build a dynamic query that acts like (SELECT property FROM table WHERE property Like '%value1%' OR property Like '%value2%' ... )
In my previous post, I had a chance to work with "OrderBy" extension done by Adams Anderson and Marc Gravell (click here for details). This sample was my inspiration to write this blog which uses the same technique to solve my problem that has been bothering me for a while.
Basically, I was not able to find a simple way to dynamically build the following SQL statement using LINQ (Lambda style):
SELECT name, title FROM user WHERE title Like '%program%' OR title Like '%admin%'
Of course, if I have a list of "exact job titles", I can do something like this:
SELECT name, title FROM user WHERE title IN ('application programmer', 'system administrator')
which can be expressed in LINQ:
string[] filters = {"application programmer", "system administrator"};
var result = dbcontext.USERS.Where(u => filters.Contains(u.title));
However, I need to dynamically build the query with Where method along with Like and Or operators. Basically, I am going to create a new extension called "WhereLikeIn" (I could not come up with a better name :) which uses expression trees to construct a query against an IQueryable data source.
First, create a WhereLikeIn calss as shown below:
Now, I can use this extension to dynamically build the following SQL query:
SELECT name, title FROM user WHERE title Like '%program%' OR title Like '%admin%'
:)
Basically, I was not able to find a simple way to dynamically build the following SQL statement using LINQ (Lambda style):
SELECT name, title FROM user WHERE title Like '%program%' OR title Like '%admin%'
Of course, if I have a list of "exact job titles", I can do something like this:
SELECT name, title FROM user WHERE title IN ('application programmer', 'system administrator')
which can be expressed in LINQ:
string[] filters = {"application programmer", "system administrator"};
var result = dbcontext.USERS.Where(u => filters.Contains(u.title));
However, I need to dynamically build the query with Where method along with Like and Or operators. Basically, I am going to create a new extension called "WhereLikeIn" (I could not come up with a better name :) which uses expression trees to construct a query against an IQueryable data source.
First, create a WhereLikeIn calss as shown below:
public static class WhereLikeInHelper { // string, string[] public static IEnumerable<T> WhereLikeIn<T>(this IEnumerable<T> enumerable, string prop, string[] strings) { return enumerable.AsQueryable().WhereLikeIn(prop, strings).AsEnumerable(); } // string, string[] public static IQueryable<T> WhereLikeIn<T>(this IQueryable<T> collection, string prop, string[] strings) { collection = ApplyWhereLikeIn<T>(collection, prop, strings); return collection; } // expression x => x.property public static IEnumerable<T> WhereLikeIn<T, TValue>(this IEnumerable<T> enumerable, Expression<Func<T, TValue>> expression, params TValue[] collectionparam) { return enumerable.AsQueryable().WhereLikeIn(expression, collectionparam).AsEnumerable(); } // expression x => x.property public static IEnumerable<T> WhereLikeIn<T, TValue>(this IEnumerable<T> enumerable, Expression<Func<T, TValue>> expression, IEnumerable<TValue> collectionparam) { return enumerable.AsQueryable().WhereLikeIn(expression, collectionparam).AsEnumerable(); } // expression x => x.property public static IQueryable<T> WhereLikeIn<T, TValue>(this IQueryable<T> collection, Expression<Func<T, TValue>> expression, params TValue[] collectionparam) { return WhereLikeIn(collection, expression, (IEnumerable<TValue>)collectionparam); } // expression x => x.property public static IQueryable<T> WhereLikeIn<T, TValue>(this IQueryable<T> collection, Expression<Func<T, TValue>> expression, IEnumerable<TValue> collectionparam) { if (expression == null) throw new ArgumentException("lambda expression is missing"); if (collectionparam == null) throw new ArgumentException("parameters missing"); if (typeof(TValue) != typeof(string)) throw new ArgumentException("property must be in string type."); // convert it to string string[] strings = collectionparam.Cast<string>().ToArray(); string prop = ((MemberExpression)(expression.Body)).Member.Name; collection = ApplyWhereLikeIn<T>(collection, prop, strings); return collection; } private static IQueryable<T> ApplyWhereLikeIn<T>(IQueryable<T> collection, string prop, string[] strings) { Type _type = typeof(T); // s => s.property ParameterExpression _arg = Expression.Parameter(_type, "s"); Expression _expr = _arg; PropertyInfo _pi = _type.GetProperty(prop); _expr = Expression.Property(_expr, _pi); _type = _pi.PropertyType; // Is string if (_type != typeof(string)) throw new ArgumentException("property must be in string type."); // Is strings emptyornull if(strings == null) throw new ArgumentException("parameters must be in string[] type."); else if(strings.Length == 0) throw new ArgumentException("parameters must contain at least one string."); // [SAMPLE] => foreach(s in strings){ s => s.property.Contains(s) } // s => s.property.Contains(one_string) Expression _param = Expression.Constant(strings[0]); Expression _predicateBody = Expression.Call(_expr, typeof(string).GetMethod("Contains"), _param); // _arg for(int i = 1; i < strings.Length; i++){ Expression _p = Expression.Constant(strings[i]); Expression _cmpr = Expression.Call(_expr, typeof(string).GetMethod("Contains"), _p); _predicateBody = Expression.OrElse(_cmpr, _predicateBody); } MethodCallExpression whereCallExpression = Expression.Call( typeof(Queryable), "Where", new Type[] { collection.ElementType }, collection.Expression, Expression.Lambda<Func<T, bool>>(_predicateBody, new ParameterExpression[] { _arg }) ); //// return collection.Provider.CreateQuery<T>(whereCallExpression); } }
Now, I can use this extension to dynamically build the following SQL query:
SELECT name, title FROM user WHERE title Like '%program%' OR title Like '%admin%'
public ActionResult GetDepartmentStaff(string filters) { // filters format: "program;admin" // string[] _filters = filters != null ? filters.Split(';') : null; var _staff = from p in db.People select p; if(_filters != null) { // p.title => "title" _staff = _staff.WhereLikeIn("title", _filters); // [Alternative ways] // _staff = _Staff.WhereLikeIn(s => s.title, _filters.AsEnumerable()); // _staff = _Staff.WhereLikeIn(s => s.title, "program", "admin"); } return Json(_staff.ToList(),JsonRequestBehavior.AllowGet); }
:)
Comments
Post a Comment