Click here to Skip to main content
Click here to Skip to main content

Using jqGrid’s search toolbar with multiple filters in ASP.NET MVC

By , 12 Feb 2010
 
Prize winner in Competition "Best ASP.NET article of February 2010"

MvcGrid_Sample

Introduction

Very often, we have a requirement to display data in tabular form and manipulate it. In classic ASP.NET, built-in controls are available. It's always better to use custom scripts to solve this problem in ASP.NET MVC. jqGrid is one of such solutions.

Background

jqGrid

jqGrid is a plugin for jQuery, which allows you to display data in tabular form with greater functionality. Its features include:

  • supports tables within tables
  • Demo1.PNG

  • supports data editing
  • Demo2.PNG

  • supports tree-like structure to display data
  • supports themes on the jQuery UI
  • records searching, filtering of each column, sorting by columns, page navigation, etc.

License

"The jqGrid is released under the GPL and MIT licenses. This license policy makes the software available to everyone for free (as in free beer), and you can use it for commercial or Open Source projects, without any restriction (the freedom above). Of course, the creation of this software has taken some time, and its maintenance will take even more. All the time we use for enhancing jqGrid is time you save on your own projects. Consider all the time you have saved by simply downloading the source file and trying to give it a price: this is the amount of money you should consider sending us to continue our good work."

Official site: trirand.com. Version 3.6.3. is available currently. Note: I use an older version. It has some bugs in IE, but in Firefox it works alright.

Using the code

Using jqGrid

Consider which files we must include to use this plug-in:

<link href="../../Scripts/jqGrid/themes/smoothness/jquery-ui-1.7.2.custom.css" 
  rel="stylesheet" type="text/css" />
<link href="../../Scripts/jqGrid/themes/ui.jqGrid.css" 
  rel="stylesheet" type="text/css" />
<script language="javascript" type="text/javascript" 
  src="<%= Url.Content("/Scripts/jquery-1.3.2.js") %>"></script>
<script language="javascript" type="text/javascript" 
  src="<%= Url.Content("/Scripts/jquery-ui-1.7.2.custom.min.js") %>">

It's obvious that we should include jQuery and JQuery UI scripts, the jqGrid plug-in, and the jqGridHomeIndex.js file which contains the grid declaration. Besides, there are some auxiliary files in the directory \Scripts\jqGrid. We must set the jqGrid settings to help the plug-in to configure itself properly for our needs. In it, we define the headers, columns, format of the data used, the size of the table, etc.:

$('#grid').jqGrid({
    colNames: ['Online', 'Computer', 'IP', 'User'],
    colModel: [
                { name: 'IsOnline', width: 100, index: 'IsOnline', 
                  searchoptions: { sopt: ['eq', 'ne']} },
                { name: 'Name', index: 'Name', 
                  searchoptions: { sopt: ['eq', 'ne', 'cn']} },
                { name: 'IP', index: 'IP', 
                  searchoptions: { sopt: ['eq', 'ne', 'cn']} },
                { name: 'User', index: 'User', 
                  searchoptions: { sopt: ['eq', 'ne', 'cn']} }
              ],
    sortname: 'Name',
    rowNum: 10,
    rowList: [10, 20, 50],
    sortorder: "asc",
    datatype: 'json',
    caption: 'Result of scanning',
    viewrecords: true,
    mtype: 'GET',
    jsonReader: {
        root: "rows",
        page: "page",
        total: "total",
        records: "records",
        repeatitems: false,
        userdata: "userdata"
    },
    url: "/Home/GetData"
})

You can find a lot of information about this plug-in in the Internet, so I won't talk about its settings.

Search

jqGrid has the ability to set filters for searching the necessary data only. For this, in its navigator panel, the following options for searching should be provided:

.navGrid('#pager', { view: false, del: false, add: false, edit: false },
   {}, // default settings for edit
   {}, // default settings for add
   {}, // delete
   {closeOnEscape: true, multipleSearch: true, 
         closeAfterSearch: true }, // search options
   {}
 );
  • multipleSearch - allows search by multiple criteria
  • closeOnEscape - allows to close the search toolbar by pressing the Escape key
  • closeAfterSearch - allows to close the search toolbar after search

But, we must also determine which operations are available for searching on a particular column of the table. It's done in the colModel section in the searchoptions param:

colModel: [
{ name: 'IsOnline', width: 100, index: 'IsOnline', 
  searchoptions: { sopt: ['eq', 'ne']} },
{ name: 'Name', index: 'Name', searchoptions: { sopt: ['eq', 'ne', 'cn']} },
{ name: 'IP', index: 'IP', searchoptions: { sopt: ['eq', 'ne', 'cn']} },
{ name: 'User', index: 'User', searchoptions: { sopt: ['eq', 'ne', 'cn']} 
}],

A set of operations is defined for each field here:

  • eq - equal
  • ne - not equal
  • cn - contains

A pop-up search window will be available after we set these settings:

SearchToolbar.PNG

The drop down list match is used to determine how these settings are linked to each other - with the help of the AND or the OR operator. "+" and "-" are used to add or delete new filtering options. The server will send an asynchronous request with the following parameters after we press the Find button:

_search = true
filters {
"groupOp":"AND",
"rules":[
{"field":"IsOnline","op":"eq","data":"True"},
{"field":"Name","op":"cn","data":"asdasd"}
   ]
}
nd = 1265873327560
page = 1
rows = 10
sidx = Name
sord = asc
  • _search - determines if filtering is used
  • filters - if filtering is used, it provides information in JSON-format on its parameters:
    • groupOp - operator which applies to a group of rules, Rules (AND and OR)
    • rules - a set of rules, where:
      • field - a field where filtering is done
      • op - an operation which the user selected
      • data - a filter which the user entered
  • page - page number
  • rows - page size
  • sidx - a field where sorting is executed
  • sord - sorting direction (asc or desc)

The request is sent by a URL which is set in the jqGrid:

mtype: 'GET',
url: "/Home/GetData"

Now the server must process the request and return the relevant data.

jqGrid processing

The method which will handle this request is declared as follows:

public JsonResult GetData(GridSettings grid)

As shown, it is served with a strongly typed object GridSettings, and in response, it returns JSON-data. In order to bring the object to the method , we use the ASP.NET MVC feature, ModelBinder, which is provided to allow Action methods to take complex types as their parameters.

First of all, we define several types, which will contain data from the jqGrid:

  • GridSettings - stores the jqGrid structure
  • public class GridSettings
    {
        public bool IsSearch { get; set; }
        public int PageSize { get; set; }
        public int PageIndex { get; set; }
        public string SortColumn { get; set; }
        public string SortOrder { get; set; }
    
        public Filter Where { get; set; }
    }
  • Filter - a filter which is defined by the user in the search toolbar
  • [DataContract]
    public class Filter
    {
        [DataMember]
        public string groupOp { get; set; }
        [DataMember]
        public Rule[] rules { get; set; }
    
        public static Filter Create(string jsonData)
        {
            try
            {
                var serializer = 
                  new DataContractJsonSerializer(typeof(Filter));
                System.IO.StringReader reader = 
                  new System.IO.StringReader(jsonData);
                System.IO.MemoryStream ms =
                  new System.IO.MemoryStream(
                  Encoding.Default.GetBytes(jsonData));
                return serializer.ReadObject(ms) as Filter;
            }
            catch
            {
                return null;
            }
        }
    }

    The factoring method Create allows to create an object of this class from the serialized JSON data.

  • Rule - represents a rule from the filter
  • [DataContract]
    public class Rule
    {
        [DataMember]
        public string field { get; set; }
        [DataMember]
        public string op { get; set; }
        [DataMember]
        public string data { get; set; }
    }

In fact, these types are identical to the corresponding structures in jqGrid. Next, we create a class GridModelBinder, which inherits from IModelBinder and overrides the BindModel method. Inside this method, we expect the HttpContext.Request object will contain a query string from jqGrid:

public class GridModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, 
                            ModelBindingContext bindingContext)
    {
        try
        {
            var request = controllerContext.HttpContext.Request;
            return new GridSettings
            {
                IsSearch = bool.Parse(request["_search"] ?? "false"),
                PageIndex = int.Parse(request["page"] ?? "1"),
                PageSize = int.Parse(request["rows"] ?? "10"),
                SortColumn =  request["sidx"] ?? "",
                SortOrder = request["sord"] ?? "asc",
                Where = Filter.Create(request["filters"] ?? "")
            };
        }
        catch
        {
            return null;
        }
    }
}

In order to call this method when accessing Home/GetData, you must add the following attribute to the GridSettings class:

[ModelBinder(typeof(GridModelBinder))]
public class GridSettings

Now a grid object in the GetData method of the Home controller will be initialized correctly.

Filtering and sorting on the server

After we have learned how to get typed form data from the jqGrid, it would be nice to handle and apply them to the collection. Reflection and expression trees will help us here. Here is some information on expression trees from MSDN:

"Expression trees represent language-level code in the form of data. The data is stored in a tree-shaped structure. Each node in the expression tree represents an expression, for example, a method call or a binary operation such as x < y. The following illustration shows an example of an expression and its representation in the form of an expression tree. The different parts of the expression are color coded to match the corresponding expression tree node in the expression tree. The different types of the expression tree nodes are also shown."

ExpressionTrees.PNG

In the LinqExtensions class, I wrote two methods that use this approach:

  • OrderBy
  • Where

The OrderBy method is used for sorting:

public static IQueryable<T> OrderBy<T>(
   this IQueryable<T> query, string sortColumn, string direction)

This is the extension method of the IQueryable<T> object, with these parameters:

  • sortColumn - the name of the column which is sorted
  • direction - direction of sorting

Let us now consider what is done in this method:

  • Define sort order:
  • string methodName = string.Format("OrderBy{0}",
      direction.ToLower() == "asc" ? "" : "descending");
  • Then, we construct the lambda-expression as p => p.Name (or System.Linq.Expressions.Expression<func<t,tkey>>). We create the parameter p, whose type is defined in ElementType.
  • ParameterExpression parameter = Expression.Parameter(query.ElementType, "p");
  • So we get a member of the class, which will be sorted. This code takes into account that a class can be a nested class. In this case, it's expected that they will be separated by a dot:
  • MemberExpression memberAccess = null;
    foreach (var property in sortColumn.Split('.'))
        memberAccess = MemberExpression.Property
           (memberAccess ?? (parameter as Expression), property);
  • The Lambda-expression is completed by the creation of the object which represents calling a method:
  • LambdaExpression orderByLambda = Expression.Lambda(memberAccess, parameter);
    MethodCallExpression result = Expression.Call(
                          typeof(Queryable),
                          methodName,
                          new[] { query.ElementType, memberAccess.Type },
                          query.Expression,
                          Expression.Quote(orderByLambda));
  • Return IQuerable<T>:
  • return query.Provider.CreateQuery<T>(result);

The Where method is used for filtering:

public static IQueryable<T> Where<T>(this IQueryable<T> query,
              string column, object value, WhereOperation operation)
  • column - name column
  • value - filtering value
  • operation - operation used for filtration

The enumeration WhereOperation is declared as follows:

public enum WhereOperation
{
    [StringValue("eq")]
    Equal,
    [StringValue("ne")]
    NotEqual,
    [StringValue("cn")]
    Contains
}

Here, I use the StringValue attribute, which allows to set the string value of the transfer line, which is very convenient in our case. More information about the StringValue attribute can be found here: StringValueAttribute by Matt Simner.

Depending on the type of operation, we must construct an appropriate lambda-expression. For this, we can use the predefined Equal and NotEqual methods of the Expression class, or use Reflection to access the desired method of another class, as we do in the case of the Contains operation:

Expression condition = null;
LambdaExpression lambda = null;
switch (operation)
{
    //equal ==
    case WhereOperation.Equal:
        condition = Expression.Equal(memberAccess, filter);
        break;
    //not equal !=
    case WhereOperation.NotEqual:
        condition = Expression.NotEqual(memberAccess, filter);
        break;
    //string.Contains()
    case WhereOperation.Contains:
        condition = Expression.Call(memberAccess,
            typeof(string).GetMethod("Contains"),
            Expression.Constant(value));
        break;
}
lambda = Expression.Lambda(condition, parameter);

By setting these methods, we can take advantage of them. During filtering, we must go through the entire collection of rules with a call to the Where method. Here is how we filter with the usage of the AND operator:

foreach (var rule in grid.Where.rules)
   query = query.Where<computer>(rule.field, rule.data,
             (WhereOperation)StringEnum.Parse(typeof(WhereOperation), rule.op));

Filtering using the OR operator:

var temp = (new List<computer>()).AsQueryable();
foreach (var rule in grid.Where.rules)
{
    var t = query.Where<computer>(
    rule.field, rule.data,
    (WhereOperation)StringEnum.Parse(typeof(WhereOperation), rule.op));
    temp = temp.Concat<computer>(t);
}
//remove repeating records
query = temp.Distinct<computer>();

Sorting is simpler:

query = query.OrderBy<computer>(grid.SortColumn, grid.SortOrder);

Return data jQuery

Now, when we have a collection of the required data, we can send it to the client. Let's see how this is done:

  • Counting the number of records which satisfy the conditions:
  • var count = query.Count();
  • Paging:
  • var data = 
      query.Skip((grid.PageIndex - 1) *grid.PageSize).Take(grid.PageSize).ToArray();
  • Convert to the data format which jqGrid expects. Here, an anonymous class can help us very much:
  • var result = new
    {
        total = (int)Math.Ceiling((double)count / grid.PageSize),
        page = grid.PageIndex,
        records = count,
        rows = (from host in data
                select new
                {
                    IsOnline = host.IsOnline.ToString(),
                    Name = host.Name,
                    IP = host.IP,
                    User = host.User,
                }).ToArray()
    };
  • Serialize in JSON format and send to the client:
  • return Json(result, JsonRequestBehavior.AllowGet);

Data source

We can use the database as a data source. To do this, we need to use ORM tools such as NHibernate (you must use the LinqToNHibernate extension) and LinqToSql, which provides the IQueryable interface.

History

  • 12 February 2010 - First version of the article.

License

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

About the Author

Ilya Builuk
Software Developer (Senior) EPAM Systems
Belarus Belarus
Member
Interested in design/development of framework functionality using the best patterns and practices.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionquestion about using EF database contextmemberJayveeJavier14 Mar '13 - 2:08 
hi ilya, i am new to the EF and instead of a generated list, i pass the EF database context, I just followed your code here and i have learned a lot. i just wonder why the DBcontext doesn't get sorted after it passed to the LinqExpressions function. But when I debug it, inside the IQueryable OrderBy function, it returns the correct result. This is the repository i have:
 

public IQueryable<Driver>> GetAllDrivers()
{
return context.Driver.ToList().AsQueryable();
}
 
Thanks.
QuestionNeed help to implement custom searchmemberMember 321208015 Nov '12 - 19:56 
Hi,
 
I need to implement custom search using above code and do not want to use toolbar search of jqgrid like following link example.
 
http://zeeshanumardotnet.blogspot.in/2012/04/jqgrid-and-mvc-3-with-custom-paging-and.html[^]
 
Can anyone help me to head in right direction?
QuestionNeed help in implementing the same in multi tier application.memberMember 321208015 Nov '12 - 6:11 
Hi,
 
Great article! Helped me a lot and directed in right direction. However I am struggling in figuring it out as How can I implement the same in multi tier application with repository pattern.
 
I do not want to implement following repetitive code in each controller action.
 
var query = _repository.Computers();
 
            //filtring
            if (grid.IsSearch)
            {
                //And
                if (grid.Where.groupOp == "AND")
                    foreach (var rule in grid.Where.rules)
                        query = query.Where<Computer>(
                            rule.field, rule.data,
                            (WhereOperation)StringEnum.Parse(typeof(WhereOperation), rule.op));
                else
                {
                    //Or
                    var temp = (new List<Computer>()).AsQueryable();
                    foreach (var rule in grid.Where.rules)
                    {
                        var t = query.Where<Computer>(
                        rule.field, rule.data,
                        (WhereOperation)StringEnum.Parse(typeof(WhereOperation), rule.op));
                        temp = temp.Concat<Computer>(t);
                    }
                    //remove repeating records
                    query = temp.Distinct<Computer>();
                }
            }
 
            //sorting
            query = query.OrderBy<Computer>(grid.SortColumn,
                grid.SortOrder);
 
            //count
            var count = query.Count();
 
            //paging
            var data = query.Skip((grid.PageIndex - 1) * grid.PageSize).Take(grid.PageSize).ToArray();
 
Rather want a repository method to handle this code.
Any suggestions and help on this is highly appreciable.
 
Thanks.
AnswerRe: Need help in implementing the same in multi tier application.memberIlya Builuk15 Nov '12 - 7:25 
Hi!
Some time ago, I was doing something like you are trying to achieve. Try to check out the following project http://catpic.codeplex.com[^] and try to debug it (web-project or unit tests).
 
Look at the following classes:
1. Expression factory which builds expression trees over different collections: http://catpic.codeplex.com/SourceControl/changeset/view/aaefa7207e7d#trunk%2fCatpic.Social%2fSocialExpressionFactory.cs[^]
2. One of the specific handlers: http://catpic.codeplex.com/SourceControl/changeset/view/aaefa7207e7d#trunk%2fCatpic.Social%2fPeople%2fPeopleHandler.cs[^]
3. Query representation: http://catpic.codeplex.com/SourceControl/changeset/view/aaefa7207e7d#trunk%2fCatpic.Social%2fRequestItem.cs[^]
See http://www.martinfowler.com/eaaCatalog/queryObject.html[^]
4. Specific repository:
http://catpic.codeplex.com/SourceControl/changeset/view/aaefa7207e7d#trunk%2fCatpic.Host%2fEngine%2fSocial%2fPeopleRepository.cs[^]
GeneralRe: Need help in implementing the same in multi tier application.memberMember 321208015 Nov '12 - 16:33 
Thanks Ilya!!!
QuestionHelp Neededmembersaini arun24 Sep '12 - 19:46 
Hi,
 
I reached here while finding solution to one of the issue with jqGrid which is bothering me from quite some time.
Can you please have a look at the question I posted in Q/A forum
Filter jqGrid by multiple conditions[^]
 
Thanks
Questionduplicate results [modified]memberkodesnipper4 Jun '12 - 21:47 
Hi Ilya, your olution realy helped me out - just 1 question,
despite adding a distinct method to the results of the OR operation, my results still have duplicate records
see stack overflow question
http://stackoverflow.com/questions/10892892/how-should-i-make-the-following-linq-generic-method-returns-distinct.lo-records[^] edited : seems the duplicate results were due to using a new list as the start point for the 'or' operation while in my code i was passing in an already prepared list of entities that had an earlier filter applied, solution was to drop the new list and use the result passed in

modified 21 Jun '12 - 17:46.

SuggestionExtending the solutionmembersahookris24 Apr '12 - 20:06 
Excellent article.
Made it to work in MVC 3.
Also extended 'where' for lt, le, gt, ge operations
GeneralMy vote of 5membermanoj kumar choubey3 Apr '12 - 23:57 
Nice
GeneralMy vote of 4memberBhargava Consultant19 Mar '12 - 20:22 
Good Approach I like it
QuestionAwesome Articlememberzyck18 Feb '12 - 16:08 
Thank you 10up
Suggestionexcellent postmemberbalaji10109 Feb '12 - 5:47 
excellent post
GeneralMy vote of 5mvpKanasz Robert19 Jan '12 - 0:46 
Great. This article is well written and saved me a lot of time. Smile | :)
QuestionValidation on searchmemberFellipe Vieira28 Dec '11 - 4:31 
Hi,
 
Thanks a lot for this article. It helped me a lot on my learning.
But I have a doubt about searching for items listed in the grid.
When I click the search button, it shows a popup, asking me to enter values for filtering, but, if I don't supply values in textfields, an exception is thrown (System.FormatException) because no filter values were supplied.
 
Is there some way we could validate the information required from the users to enter in those textboxes?
AnswerRe: Validation on searchmemberIlya Builuk28 Dec '11 - 7:48 
Hi!
Try to implement client-side validation using the following approach:
http://stackoverflow.com/questions/2004993/validation-in-jqgrid[^]
Also do not forget about server-side validation.
QuestionMuch simpler solution using System.Linq.Dynamicmemberdimitry.profus@gmail.com2 Oct '11 - 17:40 
public ActionResult CustomerGridData(GridSettings grid)
{
 
    int totalRecords;
    var data = _customerService.CustomerTable.SearchGrid(grid, out totalRecords);
 
    //converting in grid format
    var result = new
    {
        total = (int)Math.Ceiling((double)totalRecords / grid.PageSize),
        page = grid.PageIndex,
        records = totalRecords,
        rows = (from c in data
                select new
                {
                    id = c.CustomerID,
                    cell = new string[] { c.CustomerID.ToString(), c.CompanyName, c.ContactName, c.ContactTitle, c.Address, c.City, c.Region, c.PostalCode, c.Country, c.Phone, c.Fax }
 
                }).ToArray()
    };
 
    //convert to JSON and return to client
    return Json(result, JsonRequestBehavior.AllowGet);
}
 

using System.Linq.Dynamic;
 
public static class LinqExtensions
{
    public static T[] SearchGrid<T>(this IQueryable<T> query, GridSettings grid, out int totalRecords) where T : class
    {
        if (grid.IsSearch)
        {
            StringBuilder sb = new StringBuilder();
            int i = 0;
            foreach (var rule in grid.Where.rules)
            {
                string op = null;
 
                switch (rule.op)
                {
                    case "eq": op = rule.field + "={0}";
                        break;
                    case "ne": op = rule.field + "!={0}";
                        break;
                    case "lt": op = rule.field + "<{0}";
                        break;
                    case "le": op = rule.field + "<={0}";
                        break;
                    case "gt": op = rule.field + ">{0}";
                        break;
                    case "ge": op = rule.field + ">={0}";
                        break;
                    case "bw": op = rule.field + ".StartsWith({0})";
                        break;
                    case "bn": op = "!" + rule.field + ".StartsWith({0})";
                        break;
                    case "in":
                        break;
                    case "ni":
                        break;
                    case "ew": op = rule.field + ".EndsWith({0})";
                        break;
                    case "en": op = "!" + rule.field + ".EndsWith({0})";
                        break;
                    case "cn": op = rule.field + ".Contains({0})";
                        break;
                    case "nc": op = "!" + rule.field + ".Contains({0})";
                        break;
                    case "nu": op = rule.field + "==null";
                        break;
                    case "nn": op = rule.field + "!=null";
                        break;
                }
 
                if (op == null)
                    throw new NotSupportedException("rule.op=" + rule.op);
 
                op = string.Format(op,  "@" + i.ToString());
 
                sb.Append(op);
                if (rule != grid.Where.rules.Last())
                {
                    sb.Append(grid.Where.groupOp == "AND" ? "&&" : "||");
                }
                i++;
            }
 
            var predicate = sb.ToString();
            var values = grid.Where.rules.Select(r => r.data).ToArray();
            query = query.Where<T>(predicate, values);
        }
 
        //records
        totalRecords = query.Count();
 
        //sorting
        query = query.OrderBy<T>(grid.SortColumn, grid.SortOrder);
 
        //paging
        var data = query.Skip((grid.PageIndex - 1) * grid.PageSize).Take(grid.PageSize).ToArray();
 
        return data;
    }
}

AnswerRe: Much simpler solution using System.Linq.DynamicmemberMark J Bailey19 Oct '11 - 8:03 
Dimitry,
 
Very clean example and nice application of Dynamic LINQ! But how would you handle the exception thrown:
 
Operator '=' incompatible with operand types 'DateTime?' and 'String'
 
when the column is a datetime (string in colModel but DateTime? in C#/database?
 
I am sure there are other types that would throw similar exceptions.
 
As far as I can tell, there is no way to receive any user specified type information in the Filters object passed in from jqGrid either.
 
EDIT: One option I have come across specific to Dynamic LINQ and this issue is to take the offending String strArg and running through DateTime.Parse(strArg) before passing in to Dynamic LINQ. That is fine on an explicit LINQ to Entities lambda expression, but when I tried to modify your approach (including a hack I added to fudge the DateTime type selection by looking at (filter.field.IndexOf("date") >= 0)) to work the rule to come out as:
 
"dateTimeCol=DateTime.Parse(@0)"
 
I then get:
 
"LINQ to Entities does not recognize the method 'System.DateTime Parse(System.String)' method, and this method cannot be translated into a store expression."
 
So, it appears Dynamic Linq just can't handle DateTime this way?
 
Thanks,
 
Mark
QuestionThanks for this, but I have an issue..memberNirosh25 Aug '11 - 18:11 
Hi Ilya,
 
Great work..
 
I managed to get this work in a MVC3 project. In that the records were not visible, though they looks to be loaded correctly (total records count, page numbers values are correct). Then I had to set 'repeatitems: true' to them to see. Is this the right way to do that?
 
However in that same system the search popup is not move-able, do I have missed a script there? Can you get me a clue to fix that issue..
- A random opportunity is like a taller chair, those who sit hang on, those who hang on fall
 
L.W.C. Nirosh.
Colombo,
Sri Lanka.

AnswerRe: Thanks for this, but I have an issue..memberNirosh25 Aug '11 - 21:17 
okay I got everything sorted out.. I added scripts to the view, then it started to work..
- A random opportunity is like a taller chair, those who sit hang on, those who hang on fall
 
L.W.C. Nirosh.
Colombo,
Sri Lanka.

QuestionGenerated SQL not parameterizedmemberPaul Newman UK22 Jun '11 - 8:03 
I changed the app to connect to a database (Oracle 11.2, via Entity Framework and the ODAC data access components, version 11.2.0.2.30 beta).
 
When I look at the SQL submitted to the database, the filter parameters are embedded as constants, e.g.:
WHERE (N'User2' = "Extent1"."User")
instead of as parameters:
WHERE (:p__linq__0 = "Extent1"."User")
That causes the db server to parse the query each time. Correct results, but inefficient and slow.
 
Maybe the problem is in ODAC, in the way it interprets the ConstantExpression, so maybe it can't be fixed until Oracle fix it. But if I use a standard LINQ "Where" clause (i.e. not through the expression tree) it adds parameters correctly. So I guess there must be a way, I just need to mimic the expression tree that a LINQ query creates.
 
Here are the relevant lines (LinqExtensions.cs line 56):
ConstantExpression filter = Expression.Constant
(
  Convert.ChangeType(value, memberAccess.Type)
);
 
// ...and later:
condition = Expression.Equal(memberAccess, filter);
 
Any ideas? Can anyone confirm whether their queries get parameterized? Whether on Oracle or some other database.
 
Thanks
Paul
GeneralFantastic!memberMarc Merritt10 Jun '11 - 4:49 
Thanks very much for this article. I was able to adapt your code for a non-MVC project I've been working on for several months. Your solution was ideal for solving my technical issues. Smile | :)
QuestionQuestion about Date searching. [modified]memberMatthew Cuba31 May '11 - 8:22 
Very good article - it has helped me along with figuring out jqGrid. Thank you!
 
I've got two columns of data - one always has a Date and the other is nullable. Wanted to use the date picker for this so I added specified the colModel like this for those columns:
 
colModel: [ 
{name: 'FlavorPromotionStartDate', index: 'FlavorPromotionStartDate', width: 120, align: 'center',
   sorttype: 'date', formatter: 'date', formatoptions: {newformat: 'm/d/Y' },
   searchoptions: { sopt: ['eq', 'gt'], dataInit: function(elem) {
   $(elem).datepicker({ changeYear: true, changeMonth: true, showButtonPanel: true})
   }
}
}, 
colModel: [ 
{name: 'FlavorPromotionEndDate', index: 'FlavorPromotionEndDate', width: 120, align: 'center',
   sorttype: 'date', formatter: 'date', formatoptions: {newformat: 'm/d/Y' },
   searchoptions: { sopt: ['eq', 'gt'], dataInit: function(elem) {
   $(elem).datepicker({ changeYear: true, changeMonth: true, showButtonPanel: true})
   }
}
},

 
and then I added
 
[StringValue("gt")]
Greater
 
to the WhereOperation.cs file.
 
And over in the LinqExtension.cs file I added in the switch(operation) for Where:
 
case WhereOption.Greater:
   condition = Expression.GreaterThan(memberAccess, filter);
   lambda = Expression.Lambda(condition, parameter);
   break;
 
For the non-nullable date field it works great, but for the nullable data (FlavorPromotionEndDate), I get an Invalid cast from 'System.String' to 'System.Nullable`1[[System.DateTime, mscorlib, Version=4.0.0.0, Culture=neutral, ... " exception.
 
This happens over in LinqExtensions.cs where this is happening:
 
ConstantExpression filter = Expression.Constant
(
   Convert.ChangeType(value, memberAccess.Type)
);
 
Ideas?
 
Thanks,
Matt
“You can't teach people to be lazy - either they have it, or they don't.”
-Dagwood Bumstead
modified on Tuesday, May 31, 2011 7:40 PM

AnswerRe: Question about Date searching.memberMatthew Cuba1 Jun '11 - 15:27 
So a solution for this problem came from a reply to a stackoverflow post by the very smart C# compiler fellow, Eric Lippert, here[^]. Essentially, what I did was something like his post suggested:
 
    static Expression MyGreaterThan(Expression e1, Expression e2)
    {
        if (IsNullableType(e1.Type) && !IsNullableType(e2.Type))
            e2 = Expression.Convert(e2, e1.Type);
        else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type))
            e1 = Expression.Convert(e1, e2.Type);
        return Expression.GreaterThan(e1, e2);
    }
    static bool IsNullableType(Type t)
    {
        return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
    }
 
I created my own GreaterThan, GreatThanOrEqual, LessThan, LessThanOrEqual, Equal and NotEqual and refactored out the common stuff from his example. It works well, essentially taking the non-nullable type and converting it to the nullable type so that the Expression.GreaterThan (or whichever one is called) will work properly.
“You can't teach people to be lazy - either they have it, or they don't.”
-Dagwood Bumstead

GeneralRe: Question about Date searching.memberIlya Builuk1 Jun '11 - 21:42 
Thanks for your comment Smile | :)
QuestionError when use a getproperty(property) in a complex objectmemberRusito27 May '11 - 8:07 
Hi my friend, i use your code with very small modifications, but throw me some errors when the code call a type.GetProperty(prop) because the prop is in an another object of this property, for example:
 
public class A
{
string prop { get; set; }
}
 
public class B
{
string prop2 { get; set;}
A prop3 { get; set; }
}
 
when the Linq try to access prop throws an error because its not a property of A. I dont know how to fix the problem, i think the answer its only reflection but i lost.
 
PD: sorry for my bad english
QuestionHi very nice article but am getting error like thismemberNayeem Yahoo19 May '11 - 19:39 
Have downloaded ur solution and just running the project, but am getting this error while running in MvcControllerFactory.cs am getting value cannot be null, wht am missing anything, any modifications that i need to do Cry | :((
QuestionRe: Hi very nice article but am getting error like thismemberDocKevin7 Jun '11 - 5:12 
I am getting the same error. It appears that MvcControllerFactory.GetZControllerInstance is getting called when jqGrid is trying to load and it is being passed a null controllerType. I am unsure at the point why that is happening. Any help would be appreciated. Thank you.
AnswerRe: Hi very nice article but am getting error like thismemberIlya Builuk10 Jun '11 - 22:29 
Maybe the reason is changes in new version of ASP.NET MVC. I'll try to check the solution when I have spare time
AnswerRe: Hi very nice article but am getting error like thismemberAsura02714 Jul '11 - 0:35 
if (controllerType == null)
{
return null;
}
else
{
return Activator.CreateInstance(controllerType, new FakeComputersRepository()) as IController;
}
 
i fixed error by code are above. It work for me.
 
Chain Saw Electric
 
I hope it can help you pass this error.

GeneralHow to chage search window widthmemberAndrusM15 May '11 - 22:05 
My table contains 5 255 char wide fields.
 
for this table jqGrid search window width is too small, horizontal scollbar appears.
Search window contains 3 columns with equal width.
Column name and search operation width is too big.
 
How to increase search window width and decrease column name and search operand columns widths ?
Andrus

GeneralMy vote of 5memberboonyasak27 Jan '11 - 19:55 
Very cool! thk
GeneralMy vote of 5memberAlexCode26 Jan '11 - 9:19 
Very nice! Thanx!
GeneralMy vote of 5memberRaf Vandesande6 Jan '11 - 5:26 
Very usefull indeed
GeneralMy vote of 5 and a sugestion for the Where methodmemberkrazysmile29 Dec '10 - 0:52 
Hello.
Just want to thank you for the article. It help me alot.
 
Bellow is my final adaptation of your code and a few of the comments suggested.
I joined them all and make a method that suit my needs.
 

Linq Extension
public static IQueryable<T> Where<T>(this IQueryable<T> pQuery, SearchCriteria[] pCriterias, GroupOperator pGroupOp, bool pCaseSensitive)
{
    if (pCriterias.Count() == 0)
        return pQuery;
 
    LambdaExpression lambda;
    Expression resultCondition = null;
 
    // Create a member expression pointing to given column
    ParameterExpression parameter = Expression.Parameter(pQuery.ElementType, "p");
 
    foreach (var searchCriteria in pCriterias)
    {
        if (string.IsNullOrEmpty(searchCriteria.Column))
            continue;
 
        MemberExpression memberAccess = null;
        foreach (var property in searchCriteria.Column.Split('.'))
            memberAccess = MemberExpression.Property
                (memberAccess ?? (parameter as Expression), property);
 
        // Change the type of the parameter 'value'. it is necessary for comparisons (specially for booleans)
        ConstantExpression filter = Expression.Constant
        (
            Convert.ChangeType(searchCriteria.Value, memberAccess.Type)
        );
 
        //switch operation
        Expression condition = null;
        switch (searchCriteria.Operation)
        {
            //equal ==
            case WhereOperation.Equal:
                if (pCaseSensitive || searchCriteria.CaseSensitive)
                {
                    //CaseSensitive
                    condition = Expression.Equal(memberAccess, filter);
                }else{
                    //Case InSensitive
                    Expression toLower = Expression.Call(memberAccess, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
                    condition = Expression.Equal(toLower, Expression.Constant(searchCriteria.Value.ToString().ToLower()));
                }
                break;
 
            //not equal !=
            case WhereOperation.NotEqual:
                if (pCaseSensitive || searchCriteria.CaseSensitive)
                {
                    //CaseSensitive
                    condition = Expression.NotEqual(memberAccess, filter);
                }
                else
                {
                    //Case InSensitive
                    Expression toLower = Expression.Call(memberAccess, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
                    condition = Expression.NotEqual(toLower, Expression.Constant(searchCriteria.Value.ToString().ToLower()));
                }
                break;
 
            // Greater
            case WhereOperation.Greater:
                if (pCaseSensitive || searchCriteria.CaseSensitive)
                {
                    //CaseSensitive
                    condition = Expression.GreaterThan(memberAccess, filter);
                }
                else
                {
                    //Case InSensitive
                    Expression toLower = Expression.Call(memberAccess, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
                    condition = Expression.GreaterThan(toLower, Expression.Constant(searchCriteria.Value.ToString().ToLower()));
                }
                break;
 
            // Greater or equal
            case WhereOperation.GreaterOrEqual:
                if (pCaseSensitive || searchCriteria.CaseSensitive)
                {
                    //CaseSensitive
                    condition = Expression.GreaterThanOrEqual(memberAccess, filter);
                }
                else
                {
                    //Case InSensitive
                    Expression toLower = Expression.Call(memberAccess, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
                    condition = Expression.GreaterThanOrEqual(toLower, Expression.Constant(searchCriteria.Value.ToString().ToLower()));
                }
                break;
 
            // Less
            case WhereOperation.Less:
                if (pCaseSensitive || searchCriteria.CaseSensitive)
                {
                    //CaseSensitive
                    condition = Expression.LessThan(memberAccess, filter);
                }
                else
                {
                    //Case InSensitive
                    Expression toLower = Expression.Call(memberAccess, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
                    condition = Expression.LessThan(toLower, Expression.Constant(searchCriteria.Value.ToString().ToLower()));
                }
                break;
 
            // Less or equal
            case WhereOperation.LessEqual:
                if (pCaseSensitive || searchCriteria.CaseSensitive)
                {
                    //CaseSensitive
                    condition = Expression.LessThanOrEqual(memberAccess, filter);
                }
                else
                {
                    //Case InSensitive
                    Expression toLower = Expression.Call(memberAccess, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
                    condition = Expression.LessThanOrEqual(toLower, Expression.Constant(searchCriteria.Value.ToString().ToLower()));
                }
                break;
 
            //string.Contains()
            case WhereOperation.Contains:
                if (pCaseSensitive || searchCriteria.CaseSensitive)
                {
                    //CaseSensitive
                    condition = Expression.Call(memberAccess,
                                                typeof(string).GetMethod("Contains"),
                                                Expression.Constant(searchCriteria.Value));
                }
                else
                {
                    //Case InSensitive
                    Expression toLower = Expression.Call(memberAccess, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
                    condition = Expression.Call(toLower,
                                                typeof(string).GetMethod("Contains"),
                                                Expression.Constant(searchCriteria.Value.ToString().ToLower()));
                }
 
                break;
 
            default:
                continue;
        }
 

        if (pGroupOp == GroupOperator.AND)
            resultCondition = resultCondition != null ? Expression.And(resultCondition, condition) : condition;
        else
            resultCondition = resultCondition != null ? Expression.Or(resultCondition, condition) : condition;
    }
 
    lambda = Expression.Lambda(resultCondition, parameter);
 
    MethodCallExpression result = Expression.Call(
               typeof(Queryable), "Where",
               new[] { pQuery.ElementType },
               pQuery.Expression,
               lambda);
 
    return pQuery.Provider.CreateQuery<T>(result);
}
 

Usage(VB.NET):
Dim seee = 'Some IQueryable

Dim isSearch As Boolean = CType(_search, Boolean)
If (isSearch) Then
	If (filters <> String.Empty) Then
 
		Dim filter As Filter = Filter.Create(filters)
		Dim searchCriterias = (From r In filter.rules _
							   Select New SearchCriteria With _
											{.Column = r.field, _
											 .Operation = CType(StringEnum.Parse(GetType(WhereOperation), r.op), WhereOperation), _
											 .Value = r.data, _
											 .CaseSensitive = False}).ToArray()
 
		If (filter.groupOp = "AND") Then
			'AND
			seee = seee.Where(searchCriterias, GroupOperator.AND, False)
		Else
			'OR
			seee = seee.Where(searchCriterias, GroupOperator.OR, False)
		End If
	End If
End If

GeneralRe: My vote of 5 and a sugestion for the Where methodmemberkrazysmile29 Dec '10 - 1:22 
Forgot to show the changes in WhereOperation, SearchCriteria and also a enum used to the group Operator
I havent implement the operations commented.
 
Regards
 
/// <summary>
/// The supported operations in where-extension
/// </summary>
public enum WhereOperation
{
	[StringValue("eq")] Equal,
	[StringValue("ne")] NotEqual,
	[StringValue("lt")] Less,
	[StringValue("le")] LessEqual,
	[StringValue("gt")] Greater,
	[StringValue("ge")] GreaterOrEqual,
	//[StringValue("bw")] BeginsWith,
	//[StringValue("bn")] NotBeginsWith,
	//[StringValue("in")] IsIn,
	//[StringValue("ni")] IsNotIn,
	//[StringValue("ew")] EndsWith,
	//[StringValue("en")] NotEndsWith,
	[StringValue("cn")] Contains
	//[StringValue("nc")] NotContains
}
/// <summary>
/// The structure used by the new extension method
/// </summary>
public struct SearchCriteria
{
	public string Column;
	public object Value;
	public WhereOperation Operation;
	public bool CaseSensitive;
}
 
public enum GroupOperator
{ 
	AND,
	OR
}

GeneralRe: My vote of 5 and a sugestion for the Where methodmemberIlya Builuk29 Dec '10 - 8:29 
Thanks for your reply. I think it can help somebody else Smile | :)
GeneralRe: My vote of 5 and a sugestion for the Where methodmemberMatthew Cuba18 Jul '11 - 9:57 
This was VERY helpful to me. Thanks!Thumbs Up | :thumbsup:
“You can't teach people to be lazy - either they have it, or they don't.”
-Dagwood Bumstead

GeneralRe: My vote of 5 and a sugestion for the Where methodmemberMadhu Kampurath24 Nov '11 - 0:49 
This method really helped me. Many thanks.
 
In case anybody wants to implement "Starts With" and "Ends With", here is the switch case I added.
 
case WhereOperation.StartsWith:
	if (pCaseSensitive || searchCriteria.CaseSensitive)
	{
		//CaseSensitive
		condition = Expression.Call(memberAccess,
			typeof(string).GetMethod("StartsWith", new[] { typeof(string) }),
			Expression.Constant(searchCriteria.Value));
	}
	else
	{
		//Case InSensitive
		Expression toLower = Expression.Call(memberAccess, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
		condition = Expression.Call(toLower,
			typeof(string).GetMethod("StartsWith", new[] { typeof(string) }),
			Expression.Constant(searchCriteria.Value.ToString().ToLower()));
	}
 
	break;
 
	case WhereOperation.EndsWith:
		if (pCaseSensitive || searchCriteria.CaseSensitive)
		{
			//CaseSensitive
			condition = Expression.Call(memberAccess,
				typeof(string).GetMethod("EndsWith", new[] { typeof(string) }),
				Expression.Constant(searchCriteria.Value));
		}
		else
		{
			//Case InSensitive
			Expression toLower = Expression.Call(memberAccess, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
			condition = Expression.Call(toLower,
				typeof(string).GetMethod("EndsWith", new[] { typeof(string) }),
				Expression.Constant(searchCriteria.Value.ToString().ToLower()));
		}
 
	break;
 
---------------------------
 
Make a note of
new[] { typeof(string) }
after the method name.
GeneralMy vote of 5memberkrazysmile29 Dec '10 - 0:33 
very well explained and accurate.
Help me alot
GeneralMy vote of 5membernoange115 Nov '10 - 19:46 
Excellent. Thank you for this very clear article.
GeneralFix for expensive queries in the OR operationmembersdreyesg14 Sep '10 - 10:25 
Hi Ilya,
 
Great post. it really helped us a lot.
 
the only problem we found, is something performance related when using the OR operation of jqGrid.
 
We came to this solution using binaries expressions to build the OR operation.
 
Hope you find it useful.
 
Congrats!
 

 

// The structure used by the new extension method
public struct SearchCriteria
{
    public string Column;
    public object Value;
    public WhereOperation Operation;
}
 
// How to convert the rules structure to the search criteria structure
var searchCriterias = grid.Where.rules.Select(Rule => new SearchCriteria
  {
      Column = Rule.field,
      Operation =
          (WhereOperation)
          StringEnum.Parse(
              typeof (WhereOperation),
              Rule.op),
      Value = Rule.data
  }).ToArray();
 

// Usage:
query = query.WhereOr(searchCriterias);
 

// Implementation
public static IQueryable<T> WhereOr<T>( this IQueryable<T> Query, SearchCriteria [ ] Criterias )
{
    if( Criterias.Count( ) == 0 )
        return Query;
 
    LambdaExpression lambda;
    Expression resultCondition = null;
 
    // Create a member expression pointing to given column
    ParameterExpression parameter = Expression.Parameter( Query.ElementType, "p" );
        
    foreach( var searchCriteria in Criterias )
    {
        if( string.IsNullOrEmpty( searchCriteria.Column ) )
            continue;
 
        MemberExpression memberAccess = null;
        foreach( var property in searchCriteria.Column.Split( '.' ) )
            memberAccess = MemberExpression.Property
                ( memberAccess ?? ( parameter as Expression ), property );
 
        // Change the type of the parameter 'value'. it is necessary for comparisons (specially for booleans)
        ConstantExpression filter = Expression.Constant
            (
                Convert.ChangeType( searchCriteria.Value, memberAccess.Type )
            );
 
        //switch operation
        Expression condition = null;
        switch( searchCriteria.Operation )
        {
            //equal ==
            case WhereOperation.Equal:
                condition = Expression.Equal( memberAccess, filter );
                break;
            //not equal !=
            case WhereOperation.NotEqual:
                condition = Expression.NotEqual( memberAccess, filter );
                break;
            // Greater
            case WhereOperation.Greater:
                condition = Expression.GreaterThan( memberAccess, filter );
                break;
            // Greater or equal
            case WhereOperation.GreaterOrEqual:
                condition = Expression.GreaterThanOrEqual( memberAccess, filter );
                break;
            // Less
            case WhereOperation.Less:
                condition = Expression.LessThan( memberAccess, filter );
                break;
            // Less or equal
            case WhereOperation.LessEqual:
                condition = Expression.LessThanOrEqual( memberAccess, filter );
                break;
            //string.Contains()
            case WhereOperation.Contains:
                condition = Expression.Call( memberAccess,
                                            typeof( string ).GetMethod( "Contains" ),
                                            Expression.Constant( searchCriteria.Value ) );
                break;
 
            default:
                continue;
        }
 
        resultCondition = resultCondition != null ? Expression.Or( resultCondition, condition ): condition;
    }
 
    lambda = Expression.Lambda( resultCondition, parameter );
                
    MethodCallExpression result = Expression.Call(
               typeof( Queryable ), "Where",
               new [ ] { Query.ElementType },
               Query.Expression,
               lambda );
 
    return Query.Provider.CreateQuery&lt;T&gt;( result );
 
}

GeneralRe: Fix for expensive queries in the OR operationmemberIlya Builuk15 Sep '10 - 7:39 
Thanks!
If I'll use this code again I'll pay attention to your remark
GeneralRe: Fix for expensive queries in the OR operationmemberKnutMarius14 Nov '10 - 22:45 
Ilya: Thanks for a great article!
 
sdreyesg: I tried using your OR-extension, but my application keep crashing when I use the OR filter. What I see is that the query generated looks like this:
 
Where(x=> (x.y==0) Or (x.z==0))
 
I am guessing that it is the word "Or" that is misplaced here. Should it not be "||" in order to work?
 
Have you encountered a similar issue with your implementation?
GeneralFixed Handling Nulls for searchingmemberabdulkaderjeelani3 Sep '10 - 0:23 
Replace this with the existing try block in LinqExtensions.cs in method IQueryable<T> Where<T> to handle null values;
 
/*Code*/
 
try
{
Type targetType = memberAccess.Type;
 
if (memberAccess.Type.IsGenericType && memberAccess.Type.GetGenericTypeDefinition() == typeof(Nullable<>))
targetType = memberAccess.Type.GetGenericArguments()[0];
//change param value type
//necessary to getting bool from string
filter = Expression.Constant
(
Convert.ChangeType(value, targetType)
);
}
GeneralCase sensitivity [modified]memberLiqdfire26 Jul '10 - 12:25 
How would you suppose I could make it case sensitivity.

modified on Monday, July 26, 2010 8:12 PM

GeneralRe: Case sensitivitymemberLiqdfire26 Jul '10 - 15:23 
Got it by chaining an expression call to ToLower on the member access
 

Converted to VB:
 

Case WhereOperation.Contains
Dim toLower As Expression = Expression.Call(memberAccess, GetType(String).GetMethod("ToLower", System.Type.EmptyTypes))
condition = Expression.[Call](toLower, GetType(String).GetMethod("Contains"), Expression.Constant(value.ToString().ToLower))
lambda = Expression.Lambda(condition, parameter)
Exit Select

GeneralRe: Case sensitivitymemberIlya Builuk26 Jul '10 - 21:01 
thanks for sharing your improvement Wink | ;-)
GeneralImproved where clause handlingmemberDevin Garner9 Jul '10 - 10:36 
        public static bool Comparer(this object value1, object value2, WhereOperation operation)
        {
            string strValue1 = value1.ToString().ToLowerInvariant().Trim();
            string strValue2 = value2.ToString().ToLowerInvariant().Trim();
 
            double dblValue1 = -1;
            double dblValue2 = -1;
            bool areNumbers = double.TryParse(strValue1, out dblValue1) && double.TryParse(strValue2, out dblValue2);
 
            switch (operation)
            {
                case WhereOperation.Equal:
                    return areNumbers ? dblValue1 == dblValue2 : strValue1 == strValue2;
                case WhereOperation.NotEqual:
                    return areNumbers ? dblValue1 != dblValue2 : strValue1 != strValue2;
                case WhereOperation.Contains:
                    return strValue1.Contains(strValue2);
                case WhereOperation.DoesNotContain:
                    return !strValue1.Contains(strValue2);
                case WhereOperation.BeginsWith:
                    return strValue1.StartsWith(strValue2);
                case WhereOperation.DoesNotBeginWith:
                    return !strValue1.StartsWith(strValue2);
                case WhereOperation.EndsWith:
                    return strValue1.EndsWith(strValue2);
                case WhereOperation.DoesNotEndWith:
                    return !strValue1.EndsWith(strValue2);
                case WhereOperation.LessThan:
                    return areNumbers ? dblValue1 < dblValue2 : true;
                case WhereOperation.LessThanOrEqual:
                    return areNumbers ? dblValue1 <= dblValue2 : true;
                case WhereOperation.GreaterThan:
                    return areNumbers ? dblValue1 > dblValue2 : true;
                case WhereOperation.GreaterThanOrEqual:
                    return areNumbers ? dblValue1 >= dblValue2 : true;
            }
 
            return true; // default to true so we don't filter out any records if we don't understand the where condition
        }
 
	public static IQueryable<T> Where<T>(this IQueryable<T> query, string column, object value, WhereOperation operation)
	{
	    try
	    {
                if (string.IsNullOrEmpty(column))
                    return query;
 
                ParameterExpression parameter = Expression.Parameter(query.ElementType, "p");
 
                MemberExpression memberAccess = null;
                foreach (var property in column.Split('.'))
                    memberAccess = Expression.Property(memberAccess ?? (parameter as Expression), property);
 
                if (memberAccess == null)
                    return query.Where(x => true); // default to true so we don't filter out any records if we don't understand the where condition

                Expression conditional = Expression.Call(null, typeof(LinqExtensions).GetMethod("Comparer"), Expression.Convert(memberAccess, typeof(object)), Expression.Convert(Expression.Constant(value), typeof(object)), Expression.Constant(operation));
                MethodCallExpression result = Expression.Call(typeof(Queryable), "Where", new[] { query.ElementType }, query.Expression, Expression.Lambda(conditional, parameter));
 
                return query.Provider.CreateQuery<T>(result);
            }
	    catch (Exception e)
	    {
                return query.Where(x => true); // default to true so we don't filter out any records if we don't understand the where condition
            }
        }

GeneralRe: Improved where clause handlingmembertaber loveless20 Jul '10 - 7:01 
Unfortunately this change does not work with NHibernate... I have not discovered a correction yet.
GeneralI made a change to your code. I this is generaly beneficialmemberChaosSlave22 Jun '10 - 3:53 
This change removes an issue where different types with throw an error when comparing. Now the code will do it's best to cast the value, and if it fails compare them as strings
 

value = Convert.ChangeType(value, memberAccess.Type);
to
Expression field;
try
{
value = Convert.ChangeType(value, memberAccess.Type);
field = memberAccess;
 
}
catch(FormatException ex)
{
value= value.ToString();
field=Expression.Call(memberAccess,memberAccess.Type.GetMethod("ToString",new Type[0]));
}
 

and then
 
condition = Expression.Equal(field, filter);

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 12 Feb 2010
Article Copyright 2010 by Ilya Builuk
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid