简体   繁体   中英

Dynamic Linq queries for multiple runtime filters

I have a winforms app that basically loads a bunch of data from a database using entity framework and displays it on a gridview.

For this scenario, consider the entity bound to the gridview to be this one:

public class Person
{
    public string name;
    public string sex;
    public int age;
}

I'd like to give the user the option to filter the results on the gridview by using a series of filters on which he could select the column he wants to filter (age, sex, name), the operator (greater than, equal, contains, etc.) and the value.

The filter class would be something like this:

public class filter
{
    public string column;
    public string operator;
    public string value;
}

My question is: how can i apply those filters to the data being displayed on the gridview by using dynamically created linq queries?

Try using Expression trees to build lambdas dynamically against your data.

Here's an example implementation:

void Main()
{
    var fieldName = "LastName";
    var value = "test";

    var db = new List<Person>() {
        new Person() { name = "fred jones", sex = "male", age = 55 },
        new Person() { name = "samantha jones", sex = "female", age = 45 },
        new Person() { name = "cindy jones", sex = "female", age = 6 }
    };

    // single query
    db.Where( Person.GetFilter("sex", "==", "female").Compile() ).Dump();

    // OR example
    db.Where( 
        PredicateBuilder.Or<Person>(
            Person.GetFilter("sex", "==", "male"), 
            Person.GetFilter("age", "<", 50)
        ).Compile()
    ).Dump();

}
class Person
{
    public string name;
    public string sex;
    public int age;

    public static Expression<Func<Person,bool>> GetFilter<T>(string column, string @operator, T value)
    {
        var ops = new Dictionary<string, Func<Expression, Expression, Expression>>() {
            { "==", (x,y) => Expression.Equal(x,y) },
            { "<=", (x,y) => Expression.LessThanOrEqual(x,y) },
            { ">=", (x,y) => Expression.GreaterThanOrEqual(x,y) },
            { ">", (x,y) => Expression.GreaterThan(x,y) },
            { "<", (x,y) => Expression.LessThan(x,y) },
        };

        var param = Expression.Parameter(typeof(Person));
        var deref = Expression.PropertyOrField(param, column);
        var testval = Expression.Constant(value);

        return Expression.Lambda<Func<Person,bool>>(
            ops[@operator](deref, testval),
            param);
    }
}

Note I used Linqpad to test this so replace the.Dump() call with what's appropriate in your context for displaying data. It requires the PredicateBuilder class to build OR or AND statements. Also this will fail spectacularly if the wrong type is passed as the value parameter (string passed when comparing age, for example, throws "InvalidOperationException: The binary operator Equal is not defined for the types 'System.String' and 'System.Int32'.").

If anyone can tell me a better way to deal with turning a string representation of an operator into an Expression class, let me know, as I am not really happy with the above way, but it was the quickest I could think of at the time.

You might try using the Dynamic LINQ library:

http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx

Or you can conditionally add filters to your queries:

var dc = new MyDataContext(); // wrap with using block in production
var query = dc.MyTable.AsQueryable();

if(filter1)
  query = query.Where(i=>i.Name.Contains(text));

if(filter2)
  query = query.Where(i=>i.Age == age);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM