WinForms: Возможность поиска лишь по некоторым параметрам в DataGridView
В DataGridView есть таблица, взятая в БД из MySQL. Пользователь может делать поиск значений из колонок "Тип транспорта" и "Тип питания", вписав необходимые значения в TransComboBox и EatComboBox соответственно. Я хочу, чтобы была возможность сделать поиск даже с пустым параметром. К примеру, если в EatComboBox ничего не записывать, а только в TransComboBox, то не выводит ничего, а нужно, чтобы выводились элементы, удовлетворяющие запросу в TransComboBox. Надо ли в таком случае делать какую-то проверку на пустое значение в EatComboBox, и как тогда будет выглядеть решение? Заранее спасибо. Вот код:
dvSearch.RowFilter = "[Тип транспорта] LIKE '" + TransComboBox.Text +
"' AND [Тип питания] LIKE '" + EatComboBox.Text + "'";
Ответы (1 шт):
Проблема поиска по нескольким комбинациям параметров довольно остра. Я думаю, стоит расписать способы её решения.
Чтобы код был коротким, я буду именовать колонки одной буквой: 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));