简体   繁体   English

如何使用多个条件和LINQ过滤列表中的数据?

[英]How to filter data in a list using multiple conditions and LINQ?

I created this form to generate a list of students with the ability to filter by some criteria (on the left) and to display any information needed (from the right) 我创建了此表单,以生成能够按某些条件过滤的学生列表(在左侧)并显示所需的任何信息(从右侧)
清单产生表格

When the form is initializing at the start I am grabbing the whole student list with Entity Framework 表格一开始就初始化时,我正在使用Entity Framework抓取整个学生名单

List<Student> students = await context.Students.ToListAsync().ConfigureAwait(false);

And I am saving it into two lists: 我将其保存到两个列表中:

private List<Student> _listOfAllStudents = new List<Student>();
private List<Student> _filteredStudents = new List<Student>();

Then I am performing my logic against the lists like this: 然后我对这样的列表执行逻辑:

private void PrepareFilteredStudentListAccordingToFilterCheckedBoxes()
{
    _filteredStudents = _listOfAllStudents;

    if (ColonieFilterCheckBox.Checked)
    {
        _filteredStudents = _filteredStudents.Intersect(_listOfAllStudents.Where(x => x.Colonie).Select(x => x).ToList()).ToList();
    }

    if (NatationFilterCheckBox.Checked)
    {
        _filteredStudents = _filteredStudents.Intersect(_listOfAllStudents.Where(x => x.Nataion).Select(x => x).ToList()).ToList();
    }

    if (ExcursionFilterCheckBox.Checked)
    {
        _filteredStudents = _filteredStudents.Intersect(_listOfAllStudents.Where(x => x.Excursion).Select(x => x).ToList()).ToList();
    }

    //Rest of the code is omitted but you get the idea...
}  

Same logic is being done according to display checkboxes: 根据显示复选框,将执行相同的逻辑:

private void FillDataGridViewWithFilteredStudentAccordingToDisplayCheckBoxes()
{
    FilteredStudentDataGridView.Columns.Add("Id", "Numero");
    FilteredStudentDataGridView.Columns.Add("LastName", "Nom");
    FilteredStudentDataGridView.Columns.Add("FirstName", "Prenom");

    if (MiddleNameDisplayCheckBox.Checked)
    {
        FilteredStudentDataGridView.Columns.Add("MiddleName", "Nom Du Pere");
    }

    if (BirthdayDateDisplayCheckBox.Checked)
    {
        FilteredStudentDataGridView.Columns.Add("DateOfBirth", "Date De Naissance");
    }

    //Rest of the code omitted, but same concept.

    foreach (Student student in _filteredStudents)
    {
        List<object> rowsValues = new List<object>();
        foreach (object column in FilteredStudentDataGridView.Columns)
        {
            string columnName = ((DataGridViewTextBoxColumn)column).Name;

            if (columnName == "Id")
            {
                rowsValues.Add(student.StudentId);
            }

            if (columnName == "FirstName")
            {
                rowsValues.Add(student.FirstName);
            }
            //Code omitted.
        }
        object[] arrayRowsValues = rowsValues.ToArray();
        FilteredStudentDataGridView.Rows.Add(arrayRowsValues);
    }
}

I was wondering if there is a way to use LINQ instead of all those if blocks to filter the data according to my conditions? 我想知道是否有一种方法可以使用LINQ来代替所有if块来根据我的条件过滤数据?

I would definitely move the filtering to the database: 我肯定会将过滤移到数据库:

IQueryable<Student> studentQuery = context.Students;
if (ColonieFilterCheckBox.Checked) {
    studentQuery = studentQuery.Where(x => x.Colonie);
}

if (NatationFilterCheckBox.Checked) {
    studentQuery = studentQuery.Where(x => x.Nataion);
}

if (ExcursionFilterCheckBox.Checked) {
    studentQuery = studentQuery.Where(x => x.Excursion);
}

var _filteredStudents = await studentQuery.ToListAsync().ConfigureAwait(false);

If you prefer local filtering logic, you can combine the conditions a little or use Reflection to simplify the code but slow it down some. 如果您更喜欢局部过滤逻辑,则可以将条件组合一些,或使用Reflection简化代码,但会降低一些速度。

For combined conditions, you can do 对于综合条件,您可以

_filteredStudents = _listOfAllStudents
    .Where(x => (!ColonieFilterCheckBox.Checked || x.Colonie) &&
                (!NatationFilterCheckBox.Checked || x.Natation) &&
                (!ExcursionFilterCheckBox.Checked || x.Excursion)).ToList();

but that checks the CheckBox s per Student . 但这会检查每个StudentCheckBox

Instead, using Reflection, you can build the code dynamically for the filter (again, assuming you name the CheckBox controls after the fields): 相反,可以使用反射功能为过滤器动态构建代码(同样,假设您在字段后命名CheckBox控件):

IEnumerable<Student> queryStudents = _listOfAllStudents;
var xParm = Expression.Parameter(typeof(Student));

foreach (var filterField in new[] { "Colonie", "Natation", "Excursion" }) {
    if (((CheckBox)Controls.Find($"{filterField}CheckBox")).Checked) {
        var whereLambda = (Expression<Func<Student, bool>>)Expression.Lambda(Expression.PropertyOrField(xParm, filterField), xParm);
        queryStudents = queryStudents.Where(whereLambda.Compile());
    }
}
_filteredStudents = queryStudents.ToList();

For your display logic, I would rename all the check boxes to match the data field names, and then use a lot of Reflection: 对于您的显示逻辑,我将重命名所有复选框以匹配数据字段名称,然后使用大量的Reflection:

private void FillDataGridViewWithFilteredStudentAccordingToDisplayCheckBoxes() {
    var headerText = new Dictionary<string, string> { { "Id", "Numero" }, { "LastName", "Nom" }, { "FirstName", "Prenom" }, { "MiddleName", "Nom Du Pere" },
    { "DateOfBirth", "Date De Naissance" } };

    var viewColumns = new List<string> { "Id", "LastName", "FirstName" };

    foreach (var possibleColumn in headerText.Keys) {
        var displayColumns = Controls.Find(possibleColumn + "DisplayCheckBox", true);
        if (displayColumns.Length == 1) {
            if (((CheckBox)displayColumns[0]).Checked)
                viewColumns.Add(possibleColumn);
        }
    }

    //Rest of the code omitted, but same concept.

    foreach (var dataFieldName in viewColumns)
        FilteredStudentDataGridView.Columns.Add(dataFieldName, headerText[dataFieldName]);

    foreach (var student in _filteredStudents) {
        var studentType = student.GetType();
        var rowValues = new List<object>();
        foreach (var dataFieldName in viewColumns)
            rowValues.Add(studentType.GetProperty(dataFieldName).GetValue(student, null));

        FilteredStudentDataGridView.Rows.Add(rowValues.ToArray());
    }
}

Note that if you care about the order of the columns displayed, you will need some logic to order or sort the headerText.Keys . 请注意,如果您关心显示列的顺序,则需要一些逻辑来对headerText.Keys进行排序或排序。 In my asp implementation of something similar, I just have a manual list of procedure calls with data names in the order that I want, and the procedure checks if the data item is to be displayed (in viewColumns ), then adds the data and column header. 在我类似的asp实现中,我只有一个手动的过程调用列表,该过程以所需的顺序显示了数据名称,然后该过程检查是否要显示数据项(在viewColumns ),然后添加数据和列头。

You could reuse your linq expressions in a single method. 您可以在单个方法中重用linq表达式。 Try to use AsQueryable extension method from System.Linq namespace instead: 尝试改用System.Linq命名空间中的AsQueryable扩展方法:

private ICollection<Student> FilterStudents(ICollection<Student> students)
{
    var query = students.AsQueryable();

    if (ColonieFilterCheckBox.Checked)
    {
       query  = query.Where(x=>x.Colonie);
    }

    if (NatationFilterCheckBox.Checked)
    {
        query  = query.Where(x=>x.Nation);
    }

    if (ExcursionFilterCheckBox.Checked)
    {
        query  = query.Where(x=>x.Excursion);
    }

   return query.ToList();
}  

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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