Предыдущая лекция | &nbsp; | Следующая лекция
:----------------:|:----------:|:----------------:
[Ещё раз про классы. Интерфейсы.](./t6_oop_habr.md) | [Содержание](../readme.md#тема-6-основные-принципы-объектно-ориентированного-программирования) | [Шаблоны проектирования.](./t6_templates.md)

# LINQ

## Основы LINQ

**LINQ** (Language-Integrated Query) представляет простой и удобный язык запросов к источнику данных. В качестве источника данных может выступать объект, реализующий интерфейс **IEnumerable** (например, стандартные коллекции, массивы), набор данных **DataSet**, **документ XML**. Но вне зависимости от типа источника **LINQ** позволяет применить ко всем один и тот же подход для выборки данных.

Существует несколько разновидностей **LINQ**:

* **LINQ to Objects**: применяется для работы с массивами и коллекциями

* **LINQ to Entities**: используется при обращении к базам данных через технологию Entity Framework

* **LINQ to Sql**: технология доступа к данным в MS SQL Server

* **LINQ to XML**: применяется при работе с файлами XML

* **LINQ to DataSet**: применяется при работе с объектом DataSet

* **Parallel LINQ (PLINQ)**: используется для выполнения параллельных запросов

В этой главе речь пойдет прежде всего о **LINQ to Objects**.

В чем же удобство **LINQ**? Посмотрим на простейшем примере. Выберем из массива строки, начинающиеся на определенную букву и отсортируем полученный список:

```cs
// исходный массив
string[] teams = {"Бавария", "Боруссия", "Реал Мадрид", "Манчестер Сити", "ПСЖ", "Барселона"};
 
// список для результатов
var selectedTeams = new List<string>();

foreach(string s in teams)
{
    // ищем слова начинающиеся на букву "Б"
    if (s.ToUpper().StartsWith("Б"))
        selectedTeams.Add(s);
}

// сортируем получившийся список
selectedTeams.Sort();
 
foreach (string s in selectedTeams)
    Console.WriteLine(s);
```

Теперь проведем те же действия с помощью **LINQ**:

```cs
string[] teams = {"Бавария", "Боруссия", "Реал Мадрид", "Манчестер Сити", "ПСЖ", "Барселона"};
 
var selectedTeams = from t in teams // определяем каждый объект из teams как t
                    where t.ToUpper().StartsWith("Б") //фильтрация по критерию
                    orderby t  // упорядочиваем по возрастанию
                    select t; // выбираем объект
 
foreach (string s in selectedTeams)
    Console.WriteLine(s);
```

Чтобы использовать функциональность **LINQ**, убедимся, что в файле подключено пространство имен *System.LINQ*.

Итак, код стал меньше и проще. В принципе все выражение можно было бы записать в одну строку: `var selectedTeams = from t in teams where t.ToUpper().StartsWith("Б") orderby t select t`. Но для более понятной логической разбивки я поместил каждое отдельное подвыражение на отдельной строке.

Простейшее определение запроса **LINQ** выглядит следующим образом:

```
from переменная in набор_объектов
select переменная;
```

Итак, что делает этот запрос **LINQ**? Выражение `from t in teams` проходит по всем элементам массива *teams* и определяет каждый элемент как *t*. Используя переменную *t* мы можем проводить над ней разные операции.

Несмотря на то, что мы не указываем тип переменной *t*, выражения **LINQ** являются строго типизированными. То есть среда автоматически распознает, что набор *teams* состоит из объектов **string**, поэтому переменная *t* будет рассматриваться в качестве строки.

Далее с помощью оператора `where` проводится фильтрация объектов, и если объект соответствует критерию (в данном случае начальная буква должна быть "Б"), то этот объект передается дальше.

Оператор `orderby` упорядочивает по возрастанию, то есть сортирует выбранные объекты.

Оператор `select` передает выбранные значения в результирующую выборку, которая возвращается **LINQ**-выражением.

В данном случае результатом выражения **LINQ** является объект `IEnumerable<T>`. Нередко результирующая выборка определяется с помощью ключевого слова **var**, тогда компилятор на этапе компиляции сам выводит тип.

Преимуществом подобных запросов также является и то, что они интуитивно похожи на запросы языка SQL, хотя и имеют некоторые отличия.

### Методы расширения LINQ

Кроме стандартного синтаксиса `from .. in .. select` для создания запроса **LINQ** мы можем применять специальные методы расширения, которые определены для интерфейса **IEnumerable**. Как правило, эти методы реализуют ту же функциональность, что и операторы **LINQ** типа *where* или *orderby*.

Например:

```cs
string[] teams = { "Бавария", "Боруссия", "Реал Мадрид", "Манчестер Сити", "ПСЖ", "Барселона" };
 
var selectedTeams = teams
    .Where(t=>t.ToUpper().StartsWith("Б"))
    .OrderBy(t => t);
 
foreach (string s in selectedTeams)
    Console.WriteLine(s);
```

Запрос `teams.Where(t=>t.ToUpper().StartsWith("Б")).OrderBy(t => t)` будет аналогичен предыдущему. Он состоит из цепочки методов *Where* и *OrderBy*. В качестве аргумента эти методы принимают делегат или лямбда-выражение.

Не каждый метод расширения имеет аналог среди операторов **LINQ**, но в этом случае можно сочетать оба подхода. Например, используем стандартный синтаксис **LINQ** и метод расширения *Count()*, возвращающий количество элементов в выборке:

```cs
int number = (from t in teams where t.ToUpper().StartsWith("Б") select t).Count();
```

### Список используемых методов расширения LINQ

* **Select**: определяет проекцию выбранных значений
* **Where**: определяет фильтр выборки
* **OrderBy**: упорядочивает элементы по возрастанию
* **OrderByDescending**: упорядочивает элементы по убыванию
* **ThenBy**: задает дополнительные критерии для упорядочивания элементов возрастанию
* **ThenByDescending**: задает дополнительные критерии для упорядочивания элементов по убыванию
* **Join**: соединяет две коллекции по определенному признаку
* **GroupBy**: группирует элементы по ключу
* **ToLookup**: группирует элементы по ключу, при этом все элементы добавляются в словарь
* **GroupJoin**: выполняет одновременно соединение коллекций и группировку элементов по ключу
* **Reverse**: располагает элементы в обратном порядке
* **All**: определяет, все ли элементы коллекции удовлятворяют определенному условию
* **Any**: определяет, удовлетворяет хотя бы один элемент коллекции определенному условию
* **Contains**: определяет, содержит ли коллекция определенный элемент
* **Distinct**: удаляет дублирующиеся элементы из коллекции
* **Except**: возвращает разность двух коллекцию, то есть те элементы, которые создаются только в одной коллекции
* **Union**: объединяет две однородные коллекции
* **Intersect**: возвращает пересечение двух коллекций, то есть те элементы, которые встречаются в обоих коллекциях
* **Count**: подсчитывает количество элементов коллекции, которые удовлетворяют определенному условию
* **Sum**: подсчитывает сумму числовых значений в коллекции
* **Average**: подсчитывает cреднее значение числовых значений в коллекции
* **Min**: находит минимальное значение
* **Max**: находит максимальное значение
* **Take**: выбирает определенное количество элементов
* **Skip**: пропускает определенное количество элементов
* **TakeWhile**: возвращает цепочку элементов последовательности, до тех пор, пока условие истинно
* **SkipWhile**: пропускает элементы в последовательности, пока они удовлетворяют заданному условию, и затем возвращает оставшиеся элементы
* **Concat**: объединяет две коллекции
* **Zip**: объединяет две коллекции в соответствии с определенным условием
* **First**: выбирает первый элемент коллекции
* **FirstOrDefault**: выбирает первый элемент коллекции или возвращает значение по умолчанию
* **Single**: выбирает единственный элемент коллекции, если коллекция содердит больше или меньше одного элемента, то генерируется исключение
* **SingleOrDefault**: выбирает первый элемент коллекции или возвращает значение по умолчанию
* **ElementAt**: выбирает элемент последовательности по определенному индексу
* **ElementAtOrDefault**: выбирает элемент коллекции по определенному индексу или возвращает значение по умолчанию, если индекс вне допустимого диапазона
* **Last**: выбирает последний элемент коллекции
* **LastOrDefault**: выбирает последний элемент коллекции или возвращает значение по умолчанию

## Фильтрация выборки и проекция

### Фильтрация

Для выбора элементов из некоторого набора по условию используется метод *Where*. Например, выберем все _четные_ элементы, которые больше `10`.

Фильтрация с помощью операторов **LINQ**:

```cs
int[] numbers = { 1, 2, 3, 4, 10, 34, 55, 66, 77, 88 };
var evens = numbers.Where(i => i % 2 == 0 && i > 10);
```

Если выражение в методе **Where** для определенного элемента будет равно **true** (в данном случае выражение `i % 2 == 0 && i > 10`), то данный элемент попадает в результирующую выборку.

### Выборка сложных объектов

Допустим, у нас есть класс пользователя:

```cs
class User
{
    public string Name { get;set; }
    public int Age { get; set; }
    public List<string> Languages { get; set; }
    public User()
    {
        Languages = new List<string>();
    }
}
```

Создадим набор пользователей и выберем из них тех, которым больше `25` лет:

```cs
List<User> users = new List<User>
{
    new User {
        Name="Том", 
        Age=23, 
        Languages = new List<string> {
            "английский", "немецкий" }},
    new User {
        Name="Боб", 
        Age=27, 
        Languages = new List<string> {
            "английский", "французский" }},
    new User {
        Name="Джон", 
        Age=29, 
        Languages = new List<string> {
            "английский", "испанский" }},
    new User {
        Name="Элис", 
        Age=24, 
        Languages = new List<string> {
            "испанский", "немецкий" }}
};
 
var selectedUsers = users.Where(u => u.Age > 25);

foreach (User user in selectedUsers)
    Console.WriteLine($"{user.Name} - {user.Age}");
```

Консольный вывод:

```
Боб - 27
Джон - 29
```

### Сложные фильтры

Теперь рассмотрим более сложные фильтры. Например, в классе пользователя есть список языков, которыми владеет пользователь. Что если нам надо отфильтровать пользователей по языку:

Для создания запроса применяется метод *SelectMany*:

```cs
var selectedUsers = users
    .SelectMany(u => u.Languages,
                (u, l) => new { User = u, Lang = l })
    .Where(u => u.Lang == "английский" && u.User.Age < 28)
    .Select(u=>u.User);
```                          
Результат:

```
Том - 23
Боб - 27
```

Метод *SelectMany()* в качестве первого параметра принимает последовательность, которую надо проецировать, а в качестве второго параметра - функцию преобразования, которая применяется к каждому элементу. На выходе она возвращает `8` пар "пользователь - язык" `new { User = u, Lang = l }`, к которым потом применяетс фильтр с помощью *Where*.

>8 пар в конкретно этом примере, а вообще метод для каждого пользователя возвращает список объектов равный количеству языков пользователя.

### Проекция

Проекция позволяет спроектировать из текущего типа выборки какой-то другой тип. Для проекции используется оператор **select**. Допустим, у нас есть набор объектов следующего класса, представляющего пользователя:

```cs
class User
{
    public string Name { get;set; }
    public int Age { get; set; }
}
```

Но нам нужен не весь объект, а только его свойство *Name*:

```cs
List<User> users = new List<User>();
users.Add(
    new User { Name = "Sam", Age = 43 });
users.Add(
    new User { Name = "Tom", Age = 33 });
 
var names = users.Select(u => u.Name);
 
foreach (string n in names)
     Console.WriteLine(n);
```

Результат выражения **LINQ** будет представлять набор строк, поскольку метод `Select(u => u.Name)` выбирают в результирующую выборку только значения свойства *Name*.

Аналогично можно создать объекты другого типа, в том числе анонимного:

```cs
List<User> users = new List<User>();
users.Add(
    new User { Name = "Sam", Age = 43 });
users.Add(
    new User { Name = "Tom", Age = 33 });
 
var items = users.Select(u => new { 
    FirstName = u.Name, 
    DateOfBirth = DateTime.Now.Year - u.Age 
});
 
foreach (var n in items)
    Console.WriteLine($"{n.FirstName} - {n.DateOfBirth}");
```

Здесь метод _select_ создает объект базового типа (object), используя текущий объект **User**. И теперь результат будет содержать набор объектов данного типа, в котором определены два свойства: *FirstName* и *DateOfBirth*.

### Переменые в запросах и оператор let

Иногда возникает необходимость произвести в запросах **LINQ** какие-то дополнительные промежуточные вычисления. Для этих целей мы можем задать в запросах свои переменные с помощью оператора _let_:

```cs
List<User> users = new List<User>()
{
    new User { 
        Name = "Sam", Age = 43 },
    new User { 
        Name = "Tom", Age = 33 }
};
 
var people = from u in users
             let name = "Mr. " + u.Name
             select new
             {
                Name = name,
                Age = u.Age
             };
```

В данном случае создается переменная name, значение которой равно `"Mr. " + u.Name`.

Возможность определения переменных наверное одно из главных преимуществ операторов **LINQ** по сравнению с методами расширения.

### Выборка из нескольких источников

В LINQ можно выбирать объекты не только из одного, но и из большего количества источников:

Например, возьмем классы:

```cs
class Phone
{
    public string Name { get; set; }
    public string Company { get; set; }
}
class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}
```

Создадим два разных источника данных и произведем выборку:

```cs
List<User> users = new List<User>()
{
    new User { Name = "Sam", Age = 43 },
    new User { Name = "Tom", Age = 33 }
};
List<Phone> phones = new List<Phone>()
{
    new Phone {
        Name="Lumia 630", Company="Microsoft" },
    new Phone {
        Name="iPhone 6", Company="Apple"},
};
 
var people = from user in users
             from phone in phones
             select new { Name = user.Name, Phone = phone.Name };
 
foreach (var p in people)
    Console.WriteLine($"{p.Name} - {p.Phone}");
```

Консольный вывод:

```
Sam - Lumia 630
Sam - iPhone 6
Tom - Lumia 630
Tom - iPhone 6
```

Таким образом, при выборке из двух источников каждый элемент из первого источника будет сопоставляться с каждым элементом из второго источника. То есть получиться 4 пары.

## Сортировка

Для сортировки набора данных по возрастанию используется метод *OrderBy*:

```cs
int[] numbers = { 3, 12, 4, 10, 34, 20, 55, -66, 77, 88, 4 };
IEnumerable<int> orderedNumbers = numbers.OrderBy(i => i);
foreach (int i in orderedNumbers)
    Console.WriteLine(i);
```

Метод *OrderBy* принимает критерий сортировки. В данном случае в качестве критерия выступает само число.

Возьмем посложнее пример. Допустим, надо отсортировать выборку сложных объектов. Тогда в качестве критерия мы можем указать свойство класса объекта:

```cs
List<User> users = new List<User>()
{
    new User { Name = "Tom", Age = 33 },
    new User { Name = "Bob", Age = 30 },
    new User { Name = "Tom", Age = 21 },
    new User { Name = "Sam", Age = 43 }
};
 
var sortedUsers = users.OrderBy(u => u.Name);
 
foreach (User u in sortedUsers)
    Console.WriteLine(u.Name);
```

Метод *OrderBy()* сортирует по возрастанию. Для сортировки по убыванию используется метод *OrderByDescending*:

```cs
var sortedUsers = users.OrderByDescending(u => u.Name);
```

### Множественные критерии сортировки

В наборах сложных объектов иногда возникает ситуация, когда надо отсортировать не по одному, а сразу по нескольким полям. Для этого в запросе **LINQ** добавляются методы _ThenBy_ (для сортировки по возрастанию) или _ThenByDescending_ (для сортировки по убыванию):

```cs
List<User> users = new List<User>()
{
    new User { Name = "Tom", Age = 33 },
    new User { Name = "Bob", Age = 30 },
    new User { Name = "Tom", Age = 21 },
    new User { Name = "Sam", Age = 43 }
};

var result = users
    .OrderBy(u => u.Name)
    .ThenBy(u => u.Age);

foreach (User u in result)
    Console.WriteLine($"{u.Name} - {u.Age}");
```

Результат программы:

```
Alice - 28
Bob - 30
Sam - 43
Tom - 21
Tom - 33
```

---

## Задание на дом:

1. Реализовать примеры из лекции на своей предметной области (приветствуется использование методов не рассмотренных в рамках лекции: distinct, sum и т.п.).

Предыдущая лекция | &nbsp; | Следующая лекция
:----------------:|:----------:|:----------------:
[Ещё раз про классы. Интерфейсы.](./t6_oop_habr.md) | [Содержание](../readme.md#тема-6-основные-принципы-объектно-ориентированного-программирования) | [Шаблоны проектирования.](./t6_templates.md)
