WinForms: Возможность поиска лишь по некоторым параметрам в DataGridView

В DataGridView есть таблица, взятая в БД из MySQL. Пользователь может делать поиск значений из колонок "Тип транспорта" и "Тип питания", вписав необходимые значения в TransComboBox и EatComboBox соответственно. Я хочу, чтобы была возможность сделать поиск даже с пустым параметром. К примеру, если в EatComboBox ничего не записывать, а только в TransComboBox, то не выводит ничего, а нужно, чтобы выводились элементы, удовлетворяющие запросу в TransComboBox. Надо ли в таком случае делать какую-то проверку на пустое значение в EatComboBox, и как тогда будет выглядеть решение? Заранее спасибо. Вот код:

dvSearch.RowFilter = "[Тип транспорта] LIKE '" + TransComboBox.Text + 
"' AND [Тип питания] LIKE '" + EatComboBox.Text + "'"; 

Ответы (1 шт):

Автор решения: Alexander Petrov

Проблема поиска по нескольким комбинациям параметров довольно остра. Я думаю, стоит расписать способы её решения.

Чтобы код был коротким, я буду именовать колонки одной буквой: A, B, C.
Также одной буквой - a, b, c - будут заданы значения для поиска.

То есть a = TransComboBox.Text и т. п. А если значение в комбобоксе не выбрано, то a = null.

Также для удобства используем интерполяцию строк.


Во-первых, можно просто вручную перечислить все возможные комбинации.

string filter = null;

if (a != null && b != null)
    filter = $"[A] LIKE '{a}' AND [B] LIKE '{b}'";
else if (a != null && b == null)
    filter = $"[A] LIKE '{a}'";
else if (a == null && b != null)
    filter = $"[B] LIKE '{b}'";

dvSearch.RowFilter = filter;

Надеюсь, вы пишете не под древний .NET Framework. В последних версиях C# появился паттерн-матчинг, с помощью которого такой код записывается лаконично и понятно:

string? filter = (a, b) switch
{
    (not null, not null) => $"[A] LIKE '{a}' AND [B] LIKE '{b}'",
    (not null, null)     => $"[A] LIKE '{a}'",
    (null, not null)     => $"[B] LIKE '{b}'",
    (null, null)         => null
};

При двух параметрах получаем четыре комбинации (4-я - filter = null). При трёх - восемь. Если у вас 6 параметров, то возможных комбинаций будет 64. Вручную всё это описывать, а главное потом поддерживать - мрак... И даже switch expression не спасёт.


Следующий способ - использование условий в самом языке запросов.

string filter = $"([A] LIKE '{a}' OR '{a}' = '') AND ([B] LIKE '{b}' OR '{b}' = '')";

Выглядит довольно сложно. Перепишу в несколько строк:

string filter = @$"
    ([A] LIKE '{a}' OR '{a}' = '')
AND ([B] LIKE '{b}' OR '{b}' = '')
AND ([C] LIKE '{c}' OR '{c}' = '')
";

В таком виде даже множество комбинаций параметров записываются достаточно кратко.
Такой способ иногда используют в SQL-запросах в РСУБД. За подробностями отсылаю к статье Dynamic Search Conditions in T‑SQL. У него может быть плохая производительность на больших объёмах. Но у вас в DataTable на клиенте данных в любом случае мало, так что можно смело использовать.


Другой способ - составление запроса на лету. Наверное, самый любимый способ большинства начинающих (да и любых) разработчиков.

string filter = null;

if (a != null)
    filter = $"[A] LIKE '{a}' AND ";

if (b != null)
    filter += $"[B] LIKE '{b}' AND ";

if (c != null)
    filter += $"[C] LIKE '{c}' AND ";

if (filter != null)
    filter = filter[..^5]; // удаляем последний ' AND '

В данном конкретном случае этот способ оказывается несложным и лёгким для понимания. Однако, когда нужны не только простые сравнения, но и попадание в диапазон значений (> и <) и объединений разных условий and и or, тогда составление запроса путём конкатенации строк может стать очень сложной задачей с удалением ставших ненужными частей.
В нашем случае нужно лишь удалить в конце лишний AND, что просто.

Покажу ещё один способ конструирования фильтра. Просто, чтобы был:

List<string> parts = new();

if (a != null)
    parts.Add($"[A] LIKE '{a}'");

if (b != null)
    parts.Add($"[B] LIKE '{b}'");

if (c != null)
    parts.Add($"[C] LIKE '{c}'");

string filter = string.Join(" AND ", parts);

Ещё один способ - использование LINQ.
Большое достоинство LINQ - композируемость запросов. Это огромное преимущество при работе с настоящими базами данных. Можно динамически добавлять любые условия, фильтрацию, сортировку, отображение, мапинг. Итоговый запрос будет сконструирован автоматически.

var query = dataTable.AsEnumerable();

if (a != null)
    query = query.Where(row => row.Field<string>("A") == a);

if (b != null)
    query = query.Where(row => row.Field<string>("B") == b);

if (c != null)
    query = query.Where(row => row.Field<string>("C") == c);
                        
var result = query.CopyToDataTable();            
dataGridView.DataSource = result;

Но тут мы получаем ещё один DataTable, а вы используете DataView. Могут быть нюансы в поведении.
А главное, тут используется прямое сравнение == двух значений. Между тем у вас используется LIKE.

Для имитации LIKE придётся подключить регулярки.

string patternA = $".*{a}.*";
string patternB = $".*{b}.*";
string patternC = $".*{c}.*";

Создаём шаблоны для каждого значения. Здесь wildcards и в начале, и в конце. Поправьте шаблоны, как нужно вам.

После чего используем их в запросах:

query = query.Where(row => Regex.IsMatch(row.Field<string>("A"), patternA));
→ Ссылка