Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate LINQ or Lambda Expression for Multiple columns based on the user selection of checkboxes

Tags:

c#

I have a few checkboxes on top of my webpage which correspond to the columns of my Table. Like there is a column studentId then there will be a checkbox studentId and so on. I want to write such a linq/lamda expression for List which will filter the on the basis of the checkboxes selected. For example if selects studentId and studentType checkbox then linq/lambda expression should bring all the rows matching the selection.

example:

If studentId and studentType checked then:

foreach (Child c in SomeList)
{
if (chkStudentId.checked)
{
 List.FindAll (h=> h.StudentId == c.studentId);
}
if (chkStudentType.checked)
{
 List.FindAll (h => h.StudentType == c.studentType)
}
}
}

I can't figure out how am I going to write such a code that if user selects multiple checkboxes, the query should compare to all the columns and bring the values based only on the checkboxes checked. The above is only static and does not help. please help. Thanks.

like image 285
Ebad Masood Avatar asked Dec 05 '25 14:12

Ebad Masood


2 Answers

Expression trees are a great help if you want your query to be totally dynamic. But if the number of checkboxes is static you can also choose for the following solution:

var students = <your list of students>.AsQueryable();

if ( chkStudentId.checked)
{
    students = students.Where(s => s.StudentId == c.StudentId);
}

if (chkStudentType.checked))
{
    students = students.Where(s => s.StudentType== h.StudentType);
}

In such a way you can combine the where clauses in a dynamic way.

like image 78
Wouter de Kort Avatar answered Dec 07 '25 04:12

Wouter de Kort


Disclaimer: I'm fairly new to this and there is probably much better ways to do this. Any feedback is higlhy appreciated.

Note that this method has no error/null checking. Since it uses reflection, you should profile it if you plan to use it in production.

public static IEnumerable<T> Filter<T>(IEnumerable<T> collection, Dictionary<string, object> filters) {
    var type = typeof (T);
    var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
    var queryable = collection.AsQueryable();
    var instance = Expression.Parameter(type, "instance");

    var expressions = new Stack<Expression>();

    foreach (var filter in filters) {
        var propertyName = filter.Key;
        var property = properties.FirstOrDefault(x => x.Name == propertyName);
        if (property == null)
            continue;

        var left = Expression.Property(instance, property);
        var right = Expression.Constant(filter.Value, property.PropertyType);
        var expr = Expression.Equal(left, right);

        expressions.Push(expr);
    }

    Expression call = null;
    Expression previousExpression = null;
    while(expressions.Count > 0) {
        var expr = expressions.Pop();
        if(previousExpression == null) {
            previousExpression = expr;
            call = expr;
        } else {
            var and = Expression.AndAlso(previousExpression, expr);
            call = and;
            previousExpression = and;
        }
    }

    var whereCallExpression = Expression.Call(
        typeof(Queryable),
        "Where",
        new[] { queryable.ElementType },
        queryable.Expression,
        Expression.Lambda<Func<T, bool>>(call, new[] { instance }));

    return queryable.Provider.CreateQuery<T>(whereCallExpression);
}

It overates all filters and tries to find a matching property. If it does find a property, it creates a EqualExpression which compares the actual value and the value you want to filter by. It then creates a MethodCallExpression which is passed to the query provider.

Theese expressions are then combined. I think the part with the stack is wrong, and that there is a better way to do it.

Usage:

var persons = new List<Person> {new Person {Name = "Alex", Age = 22}, new Person {Name = "Jesper", Age = 30}};
var filters = new Dictionary<string, object>();
filters.Add("Name", "Alexander Nyquist");

var results = Filter(persons, filters);

Since it's building expressions, it does works with Linq 2 Sql (tested) and probably Entity Framework. Linq 2 sql produces the following query:

SELECT [t0].[Id], [t0].[Name], [t0].[Email]
FROM [dbo].[Persons] AS [t0]
WHERE [t0].[Name] = @p0
-- @p0: Input VarChar (Size = 8000; Prec = 0; Scale = 0) [Alexander Nyquist]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1

Hope this helps.

like image 35
alexn Avatar answered Dec 07 '25 04:12

alexn