Соединение в LINQ используется для объединения двух разнотипных наборов в один. Для соединения используется оператор join или метод Join(). Как правило, данная операция применяется к двум наборам, которые имеют один общий критерий.
Оператор join имеет следующий формальный синтаксис:
from объект1 in набор1 join объект2 in набор2 on объект2.свойство2 equals объект1.свойство1
После оператора join идет выборка объектов из второй коллекции. После оператора on
указывается критерий соединения -
свойство объекта из второй выборки, а после оператора equals
- свойство объекта из первой выборки, которому должно быть равно
свойство объекта из второй выборки. Если эти свойства равны, то оба объекта попадают в финальный результат.
Например, у нас есть два класса:
record class Person(string Name, string Company); record class Company(string Title, string Language);
Класс Person представляет пользователя и хранит два свойства: Name (имя) и Company (компания пользователя). Класс Company представляет компанию и хранит два свойства: Title (название компании) и Language (основной язык программирования в компании)
Объекты обоих классов будет иметь один общий критерий - название компании. Соединим по этому критерию два набора этих классов:
Person[] people = { new Person("Tom", "Microsoft"), new Person("Sam", "Google"), new Person("Bob", "JetBrains"), new Person("Mike", "Microsoft"), }; Company[] companies = { new Company("Microsoft", "C#"), new Company("Google", "Go"), new Company("Oracle", "Java") }; var employees = from p in people join c in companies on p.Company equals c.Title select new { Name = p.Name, Company = c.Title, Language = c.Language }; foreach (var emp in employees) Console.WriteLine($"{emp.Name} - {emp.Company} ({emp.Language})"); record class Person(string Name, string Company); record class Company(string Title, string Language);
С помощью выражения
join c in companies on p.Company equals c.Title
объект p из списка people (то есть объект Person) соединяется с объектом c из списка companies (то есть с объектом Company),
если значение свойства p.Company
совпадает со значением свойства c.Title
.
Результатом соединения будет объект анонимного типа, который будет содержать три свойства. В итоге мы получим следующий вывод:
Tom - Microsoft (C#) Sam - Google (Go) Mike - Microsoft (C#)
Обратите внимание, что в массиве people есть объект new Person("Bob", "JetBrains")
, но в массиве компаний компании с именем "JetBrains"
нет, соответственно он не попал с результат. Аналогично в списке people нет объектов Person, которые бы соотствовали компании new Company("Oracle", "Java")
.
В качестве альтернативы можно было бы использовать метод
Join(IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,TInner,TResult> resultSelector);
Метод Join()
принимает четыре параметра:
второй список, который соединяем с текущим
делегат, который определяет свойство объекта из текущего списка, по которому идет соединение
делегат, который определяет свойство объекта из второго списка, по которому идет соединение
делегат, который определяет новый объект в результате соединения
Перепишим предыдущий пример с использованием метода Join:
Person[] people = { new Person("Tom", "Microsoft"), new Person("Sam", "Google"), new Person("Bob", "JetBrains"), new Person("Mike", "Microsoft"), }; Company[] companies = { new Company("Microsoft", "C#"), new Company("Google", "Go"), new Company("Oracle", "Java") }; var employees = people.Join(companies, // второй набор p => p.Company, // свойство-селектор объекта из первого набора c => c.Title, // свойство-селектор объекта из второго набора (p, c) => new { Name = p.Name, Company = c.Title, Language = c.Language }); // результат foreach (var emp in employees) Console.WriteLine($"{emp.Name} - {emp.Company} ({emp.Language})"); record class Person(string Name, string Company); record class Company(string Title, string Language);
Метод GroupJoin() кроме соединения последовательностей также выполняет и группировку.
GroupJoin(IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter, IEnumerable<TInner>,TResult> resultSelector);
Метод GroupJoin()
принимает четыре параметра:
второй список, который соединяем с текущим
делегат, который определяет свойство объекта из текущей коллекции, по которому идет соединение и по которому будет идти группировка
делегат, который определяет свойство объекта из второй коллекции, по которому идет соединение
делегат, который определяет новый объект в результате соединения. Этот делегат получает группу - объект текущей коллекции, по которому шла группировка, и набор объектов из второй коллекции, которые сооставляют группу
Например, возьмем выше определенные массивы people и companies и сгуппируем всех пользователей по компаниям:
Person[] people = { new Person("Tom", "Microsoft"), new Person("Sam", "Google"), new Person("Bob", "JetBrains"), new Person("Mike", "Microsoft"), }; Company[] companies = { new Company("Microsoft", "C#"), new Company("Google", "Go"), new Company("Oracle", "Java") }; var personnel = companies.GroupJoin(people, // второй набор c => c.Title, // свойство-селектор объекта из первого набора p => p.Company, // свойство-селектор объекта из второго набора (c, employees) => new // результат { Title = c.Title, Employees = employees }); foreach (var company in personnel) { Console.WriteLine(company.Title); foreach(var emp in company.Employees) { Console.WriteLine(emp.Name); } Console.WriteLine(); } record class Person(string Name, string Company); record class Company(string Title, string Language);
Результатом выполнения программы будет следующий вывод:
Microsoft Tom Mike Google Sam Oracle
Метод GroupJoin, также как и метод Join, принимает все те же параметры. Только теперь в последний параметр - делегат передаются объект компании и набор пользователей этой компании.
Обратите внимание, что для компании "Oracle" в массиве people нет пользователей, хотя для нее также создается группа.
Аналогичного результата можно добитьс и с помощью оператора join:
var personnel = from c in companies join p in people on c.Title equals p.Company into g select new // результат { Title = c.Title, Employees = g };
Метод Zip() последовательно объединяет соответствующие элементы текущей последовательности со второй последовательностью, которая передается в метод в качестве параметра. То есть первый элемент из первой последовательности объединяется с первым элементом из второй последовательности, второй элемент из первой последовательности соединяется со вторым элементом из второй последовательности и так далее. Результатом метода является коллекция кортежей, где каждый кортеж хранит пару соответствующих элементов из обоих последовательностей:
var courses = new List<Course> { new Course("C#"), new Course("Java") }; var students = new List<Student> { new Student("Tom"), new Student("Bob") }; var enrollments = courses.Zip(students); foreach (var enrollment in enrollments) Console.WriteLine($"{enrollment.First} - {enrollment.Second}"); record class Course(string Title); // учебный курс record class Student(string Name); // студент
Здесь метод Zip объединяет соответствующие элементы из списков courses и students. В результате создается новая коллекция, которая хранит набор кортежей. Каждый кортеж в ней имеет два элемента. Первый элемент из свойства First представляет объект текущей коллекции (в данном случае объект Course), а второй элемент (в свойстве Second) хранит объект второй последовательности (в данном случае объект Student). Консольный вывод:
Course { Title = C# } - Student { Name = Tom } Course { Title = Java } - Student { Name = Bob }