Содержание
LINQ (Language Integrated Query) в C# — это простой и удобный интегрированный язык запросов к источнику данных. Причем в качестве источника данных LINQ могут выступать, как обычные массивы или списки, так и XML-документы, наборы данных DataSet и т.д. Запросы в LINQ очень сильно напоминают запросы в SQL. В этой части мы рассмотрим основные моменты работы с LINQ в C# и научимся использовать основные возможности LINQ при работе с наборами данных,такими как массив или список.
Архитектура
Используя в своих приложениях LINQ, мы можем смешивать данные, полученные из различных источников данных единообразным способом. Например, мы можем выполнить запрос к базе данных и получить коллекцию объектов, затем выполнить второй запрос LINQ на получение аналогичных данных из XML-документа и, затем, объединить об набора данных в одну коллекцию. И при этом, выполнение этих операций будет выглядеть в коде одинаково просто и понятно.
В LINQ для каждого источника данных (базы данных, XML-документа, объекта в памяти и т.д.) используются свои поставщики LINQ или, как их ещё называют, провайдеры LINQ.
Провайдеры LINQ — это программные компоненты, позволяющие выполнять запросы к определённому источнику данных с использованием синтаксиса LINQ в .NET. То цель провайдера LINQ — это преобразовать запрос LINQ в запрос, понятный источнику данных.
Можно выделить следующие основные провайдеры LINQ:
- LINQ to Objects — работа с массивами и коллекциями в памяти
- LINQ to Entities — работа с различными базами данных, с использованием технологии Entity Framework
- LINQ to XML — работа с файлами XML
- LINQ to DataSet — работа с объектами
DataSet - Parallel LINQ (PLINQ) — выполнение параллельных запросов LINQ.
В общих чертах, работу LINQ можно представить следующим образом:
Примеры использования LINQ в C#
Выбор из массива строк с использованием LINQ
Чтобы получить возможность работать с LINQ, в проекте необходимо подключить пространство имен System.Linq. Для того, чтобы разобраться с тем,что из себя представляет LINQ и как его использовать в своих проектах C# рассмотрим решение такой простой задачи: необходимо выбрать из массива строк все строки, длина которых больше пяти символов. Без использования LINQ мы бы написали примерно такой код:
string[] strings = { "мир", "Привет", "Строка", "C#", "LINQ"};
List<string> result = new List<string>();
foreach (string s in strings)
{
if (s.Length > 5)
{
result.Add(s);
}
}
foreach (string s in result)
Console.WriteLine(s);
C LINQ этот код можно сделать короче и выразительнее:
var res = from s in strings
where s.Length > 5
select s;
foreach (string s in res)
{
Console.WriteLine(s);
}
Как можно видеть, синтаксис запроса в LINQ очень сильно похож на SQL, хотя и имеет некоторые различия. При этом, наиболее простой запрос в LINQ выглядит следующим образом:
from переменная in набор_объектов select переменная;
Если переводить запрос LINQ на обычный язык, но на нашем примере получится следующее:
from s in strings
«все элементы s из набора strings»
where s.Length > 5
«у которых длина более 5»
select s
«поместить в res«.
Здесь в примере мы использовали неявно типизированную переменную (res), хотя, могли бы использовать и явный тип. Так как в качестве источника данных В качестве источника данных для LINQ может выступать любой объект, реализующий интерфейс IEnumerable, то переписать пример мы могли бы так:
IEnumerable<string> res = from s in strings
where s.Length > 5
select s;
Сортировка массива с использованием LINQ
Ещё один показательный пример — использование LINQ для сортировки данных, например, массива чисел. В разделе «Алгоритмы» представлено несколько алгоритмов, реализующих сортировку массива чисел. Например, рассмотрим код, реализующий сортировку выбором:
//intArray - это массив целых чисел
int indx; //переменная для хранения индекса минимального элемента массива
for (int i = 0; i < intArray.Length; i++) //проходим по массиву с начала и до конца
{
indx = i; //считаем, что минимальный элемент имеет текущий индекс
for (int j = i; j < intArray.Length; j++) //ищем минимальный элемент в неотсортированной части
{
if (intArray[j] < intArray[indx])
{
indx = j; //нашли в массиве число меньше, чем intArray[indx] - запоминаем его индекс в массиве
}
}
if (intArray[indx] == intArray[i]) //если минимальный элемент равен текущему значению - ничего не меняем
continue;
//меняем местами минимальный элемент и первый в неотсортированной части
int temp = intArray[i]; //временная переменная, чтобы не потерять значение intArray[i]
intArray[i] = intArray[indx];
intArray[indx] = temp;
}
Это самый простой метод сортировки, однако, используя LINQ мы можем отсортировать массив буквально в одну строку:
int[] numbers = { 0, 100, 999, 345, -100, 1, 0, 9, 7, 6, 5, 4, 3, 2, 1, 67, 88 };
var sort = from i in numbers orderby i select i;
массив будет отсортирован в порядке возрастания.
Аналогичным образом мы можем работать с любыми наборами данных, главное, чтобы в качестве набора выступал объект, реализующий интерфейс IEnumerable. Кроме SQL-подобного стиля LINQ также предоставляет нам в распоряжение такую возможность, как использование методов расширения, определенных для интерфейса IEnumerable.
Методы расширения LINQ
Когда вы подключаете в проект пространство имен System.Linq, то все объекты, реализующие интерфейс IEnumerable, автоматически «обрастают» методами расширения LINQ. По сути, методы расширения LINQ позволяют избавиться от SQL-подобного стиля в коде вашего проекта. Вот, например, как бы выглядел пример, представленный выше, если бы мы использовали методы расширения:
int[] numbers = { 0, 100, 999, 345, -100, 1, 0, 9, 7, 6, 5, 4, 3, 2, 1, 67, 88 };
var sort = numbers.OrderBy(t => t);
foreach (int t in sort)
{
Console.Write($"{t} ");
}
В качестве параметров, методы расширения принимаю обычно делегат или лямбда-выражение. Также, используя методы расширения мы можем расширить варианты использования LINQ, например, мы можем отфильтровать массив строк и вывести количество слов в выборке, используя вот такую последовательность методов расшения:
string[] strings = { "мир", "Привет", "Строка", "C#", "LINQ"};
int count = strings.Where(s => s.Length > 5).Count();
Console.WriteLine($"В итоговой выборке {count} слов");
Наиболее часто используемые методы расширения LINQ представлены ниже:
Select— определяет проекцию выбранных значенийSelectMany— проецирует каждый элемент последовательности в объектIEnumerable<T>и объединяет результирующие последовательности в одну последовательностьWhere— определяет фильтр выборкиOrderBy— упорядочивает элементы по возрастаниюOrderByDescending— упорядочивает элементы по убываниюThenBy— задает дополнительные критерии для упорядочивания элементов возрастаниюThenByDescending— задает дополнительные критерии для упорядочивания элементов по убываниюJoin— соединяет две коллекции по определенному признакуGroupBy— группирует элементы по ключуToLookup— группирует элементы по ключу, при этом все элементы добавляются в словарьGroupJoin— выполняет одновременно соединение коллекций и группировку элементов по ключуReverse— располагает элементы в обратном порядкеAll— определяет, все ли элементы коллекции удовлетворяют определенному условиюAny— определяет, удовлетворяет хотя бы один элемент коллекции определенному условиюContains— определяет, содержит ли коллекция определенный элементDistinct— удаляет дублирующиеся элементы из коллекцииExcept— возвращает разность двух коллекций, то есть те элементы, которые создаются только в одной коллекцииUnion— объединяет две однородные коллекцииIntersect— возвращает пересечение двух коллекций, то есть те элементы, которые встречаются в обоих коллекцияхCount— подсчитывает количество элементов коллекции, которые удовлетворяют определенному условиюSum— подсчитывает сумму числовых значений в коллекцииAverage— подсчитывает среднее значение числовых значений в коллекцииMin— находит минимальное значениеMax— находит максимальное значениеTake— выбирает определенное количество элементовSkip— пропускает определенное количество элементовTakeWhile— возвращает цепочку элементов последовательности, до тех пор, пока условие истинноSkipWhile— пропускает элементы в последовательности, пока они удовлетворяют заданному условию, и затем возвращает оставшиеся элементыConcat— объединяет две коллекцииZip— объединяет две коллекции в соответствии с определенным условиемFirst— выбирает первый элемент коллекцииFirstOrDefault— выбирает первый элемент коллекции или возвращает значение по умолчаниюSingle— выбирает единственный элемент коллекции, если коллекция содержит больше или меньше одного элемента, то генерируется исключениеSingleOrDefault— выбирает единственный элемент коллекции или возвращает значение по умолчаниюElementAt— выбирает элемент последовательности по определенному индексуElementAtOrDefault— выбирает элемент коллекции по определенному индексу или возвращает значение по умолчанию, если индекс вне допустимого диапазонаLast— выбирает последний элемент коллекцииLastOrDefault— выбирает последний элемент коллекции или возвращает значение по умолчанию
Об этих методах расширения LINQ более подробно в следующих статьях.
Итого
Использование возможностей LINQ в C# позволяет сделать ваш код более выразительным (понятным) для чтения и, одновременно, более лаконичным. В своих проектах вы можете использовать LINQ в SQL-стиле (что встречается довольно редко) или же использовать методы расширения, которые во многом повторяют операции выполняемые при SQL-подобном использовании LINQ, но и, при этом, упрощают использование LINQ, например, позволяя вычислять значения элементарных математических операций — подсчитывать средние значения, определять количество элементов выборки и так далее.
