基于用户选择的复选框,生成多列的LINQ或Lambda表达式

3
我网页顶部有几个复选框与我的表格中的列对应。例如:如果有一个名为studentId的列,则会有一个名为studentId的复选框,以此类推。我想编写一个针对List的Linq/Lambda表达式,根据选中的复选框筛选数据。例如,如果选择了studentId和studentType复选框,则Linq/Lambda表达式应该带回所有符合这些选项的行。
示例:
如果选中了studentId和studentType,则:
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)
}
}
}

我无法想象如何编写这样的代码,如果用户选择多个复选框,则查询应比较所有列,并仅基于选中的复选框带出值。以上内容仅为静态内容,没有帮助,请帮忙解决。谢谢。

5个回答

3

如果您希望查询完全动态,则表达式树是很有帮助的。但是,如果复选框的数量是静态的,您也可以选择以下解决方案:

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);
}

以此方式,您可以动态地组合where子句。

我刚刚尝试了这个。当我调试并查看where子句的结果时,列表为空并且展开时显示“不产生结果”。有什么想法吗? - Ebad Masood
你开始筛选之前有学生数据吗?你检查过哪个筛选器没有匹配任何项吗?或者你想让筛选器成为OR而不是AND筛选器吗? - Wouter de Kort
是的,在过滤之前有学生数据,并且在将我的列表转换为AsQueryable后,我验证了学生列表是否有数据。然而,在这行代码之后没有产生任何结果:students = students.Where(s => s.StudentId == c.StudentId); - Ebad Masood
你能手动验证是否有符合筛选条件的项目吗?StudentId和StudentType的类型是什么? - Wouter de Kort
感谢您及时的回复。问题出在我的foreach循环上,数值不匹配。谢谢。 - Ebad Masood

1

免责声明:我对此还比较新手,可能有更好的方法。非常感谢任何反馈。

请注意,此方法没有错误/空值检查。由于它使用反射,如果您计划在生产中使用它,应该对其进行性能分析。

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);
}

它超越了所有的过滤器并尝试找到匹配的属性。如果它找到一个属性,它会创建一个EqualExpression来比较实际值和您想要过滤的值。然后它创建一个MethodCallExpression传递给查询提供程序。

这些表达式然后被组合起来。我认为堆栈部分是错误的,并且有更好的方法来解决它。

用法:

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);

由于它是构建表达式,因此它可以与 Linq 2 Sql(已测试)和可能的 Entity Framework 一起使用。 Linq 2 sql 生成以下查询:

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

希望这能有所帮助。

1
从您描述的问题来看,似乎很简单:您有一个谓词列表,根据复选框启用或禁用,然后您想聚合它们以创建单个复合过滤器。 很容易!
您的谓词如下所示:
h => h.StudentId == c.studentId
h => h.StudentType == c.studentType

但是由于复选框,你真的希望它们看起来像这样:

h => chkStudentId.Checked ? h.StudentId == c.studentId : true
h => chkStudentId.Checked ? h.StudentType == c.studentType : true

您可以将谓词扩展以包括复选框,以下是一个实现此功能的函数:

Func<CheckBox, Func<Student, bool>, Func<Student, bool>> extend =
    (cb, p) =>
        s => cb.Checked ? p(s) : true;

现在你可以像这样编写谓词列表:

var predicates = new Func<Student, bool>[]
{
    extend(chkStudentId, h => h.StudentId == c.studentId),
    extend(chkStudentType, h => h.StudentType == c.studentType),
    // etc
};

接下来的LINQ有一种简单的方法可以将一系列某些东西转换成单个东西:

Func<Student, bool>
    predicate =
        predicates.Aggregate((a, p) => s => a(s) && p(s));

现在,您只需执行此代码即可获取筛选后的列表:
var filtered = SomeList.Where(predicate);

现在,只要任何复选框发生变化,您只需要枚举“filtered”集合,就可以获得筛选后的结果。

谢谢!这个答案需要一些思考(尤其是聚合部分),但肯定比我找到的所有其他答案(不仅仅是在这里)都更有帮助,最终使我能够为我的特定需求推导出一个完全动态的解决方案,我非常满意并且表现良好!因此,如果你愿意做更多的事情而不仅仅是复制和粘贴,这就是最好的答案! - mike

0
我的解决方案是对Wouter de Kort的建议进行了一些修改,具体如下:
    var students = StudentList.AsQueryable();  

        foreach (Student sc in GroupingList)
        {  
           students = students.Where(s => (chkStudentId.Checked? h.StudentId.Trim() == sc.StudentId.Trim() : true) && (chkStudentType.Checked? h.StudentType.Trim() == sc.StudentType.Trim() : true) );
 }

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接