Содержание
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, например, позволяя вычислять значения элементарных математических операций — подсчитывать средние значения, определять количество элементов выборки и так далее.