Как выполнить LINQ-запрос к DataTable в C# .NET 3.5?
Всем привет! Я начинающий C#-разработчик, работаю с .NET Framework 3.5. У меня есть DataTable, заполненный данными из базы. Мне нужно отфильтровать строки по условию и получить список объектов. Например, есть таблица Employees с колонками ID, Name, Department, Salary. Хочу выбрать всех сотрудников из отдела "IT" с зарплатой выше 50000.
Пробовал писать так:
DataTable dt = GetEmployeesDataTable();
var result = from row in dt.Rows
where row["Department"] == "IT" && (int)row["Salary"] > 50000
select row;
Но получаю ошибку компиляции: "Could not find an implementation of the query pattern for source type 'DataRowCollection'. 'Where' not found." Понимаю, что DataTable.Rows — это не коллекция, поддерживающая LINQ. Как правильно написать запрос? Нужно ли преобразовывать DataTable во что-то другое? Буду благодарен за примеры кода.
Также интересует: можно ли использовать анонимные типы для проекции, чтобы сразу получить список Employee? И как обрабатывать DBNull в колонках? Заранее спасибо!
Решение: LINQ к DataTable через AsEnumerable()
Проблема в том, что DataTable.Rows возвращает DataRowCollection, который не реализует IEnumerable<T>, а только IEnumerable. Для LINQ-запросов нужен типизированный перечислитель. В .NET 3.5 это решается с помощью метода расширения AsEnumerable(), который находится в пространстве имен System.Data (сборка System.Data.DataSetExtensions.dll).
Шаг 1: Подключите пространство имен
using System.Data; // для AsEnumerable() и Field<T>()
Шаг 2: Используйте AsEnumerable() для DataTable
DataTable dt = GetEmployeesDataTable();
var query = from row in dt.AsEnumerable()
where row.Field<string>("Department") == "IT"
&& row.Field<int>("Salary") > 50000
select new
{
ID = row.Field<int>("ID"),
Name = row.Field<string>("Name"),
Department = row.Field<string>("Department"),
Salary = row.Field<int>("Salary")
};
foreach (var emp in query)
{
Console.WriteLine($"{emp.Name} - {emp.Salary}");
}
Объяснение ключевых моментов:
AsEnumerable()— возвращаетIEnumerable<DataRow>, что позволяет использовать LINQ.Field<T>("columnName")— типизированный доступ к колонке, который автоматически обрабатываетDBNull(возвращаетdefault(T)для null).- Вместо
row["Department"]используйтеrow.Field<string>("Department")— это безопаснее и читаемее.
Шаг 3: Преобразование в список объектов Employee
Если у вас есть класс Employee:
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public int Salary { get; set; }
}
// Запрос с проекцией в Employee
List<Employee> employees = (from row in dt.AsEnumerable()
where row.Field<string>("Department") == "IT"
&& row.Field<int>("Salary") > 50000
select new Employee
{
ID = row.Field<int>("ID"),
Name = row.Field<string>("Name"),
Department = row.Field<string>("Department"),
Salary = row.Field<int>("Salary")
}).ToList();
Обработка DBNull
Если колонка может содержать DBNull, используйте Field<T?> для nullable-типов:
var query = dt.AsEnumerable()
.Where(row => row.Field<string>("Department") == "IT")
.Select(row => new
{
Name = row.Field<string>("Name"),
Bonus = row.Field<int?>("Bonus") ?? 0 // если null, то 0
});
Совет для .NET 3.5
Убедитесь, что в проекте добавлена ссылка на System.Data.DataSetExtensions.dll (обычно она есть по умолчанию). Если нет — добавьте через меню Project → Add Reference.
Альтернативный подход: использование CopyToDataTable() и лямбда-синтаксис
Если вам нужно не только отфильтровать, но и получить новый DataTable с результатами, используйте метод CopyToDataTable(). Он доступен после AsEnumerable() и создаёт копию структуры таблицы с отфильтрованными данными.
DataTable dt = GetEmployeesDataTable();
var filteredRows = dt.AsEnumerable()
.Where(row => row.Field<string>("Department") == "IT"
&& row.Field<int>("Salary") > 50000);
DataTable resultTable = filteredRows.CopyToDataTable();
Когда это полезно:
- Нужно сохранить структуру исходной таблицы (колонки, типы).
- Результат требуется передать в другой метод, ожидающий
DataTable. - Вы хотите применить дальнейшие операции (например, сортировку) через
DataView.
Важно: CopyToDataTable() работает только с перечислителем, полученным из DataTable. Если вы попытаетесь вызвать его на произвольном IEnumerable, получите исключение.
Также можно комбинировать лямбда-синтаксис и query-синтаксис — выбирайте тот, который понятнее вашей команде. Оба подхода равнозначны по производительности.