Практическое руководство. Выполнение внутренних соединений (Руководство по программированию на C#)

В терминах реляционных баз данных внутреннее соединение возвращает результирующий набор, в котором каждый элемент первой коллекции появляется один раз для каждого соответствующего элемента второй коллекции. Если для элемента первой коллекции нет соответствующего элемента, он не появляется в результирующем наборе. Метод Join, вызываемый предложением join в C#, реализует внутреннее соединение.

В этом разделе демонстрируется выполнение следующих четырех типов внутреннего соединения.

  • Простое внутреннее соединение, которое сопоставляет элементы из двух источников данных на основании простого ключа.

  • Внутреннее соединение, которое сопоставляет элементы из двух источников данных на основании составного ключа. Составной ключ (ключ, который содержит несколько значений) позволяет сопоставлять элементы на основании нескольких свойств.

  • При выполнении множественного соединения последовательные операции соединения применяются друг к другу.

  • Внутреннее соединение, которое реализуется с помощью группового соединения.

Пример

Пример соединения с использованием простого ключа

В следующем примере создаются две коллекции, содержащие объекты двух определяемых пользователем типов Person и Pet. В C# при запросе используется предложение join для сопоставления объектов Person с объектами Pet, для которых Owner является Person. Предложение select в языке C# определяет то, как будут выглядеть результирующие объекты. В этом примере результирующие объекты — анонимные типы, состоящие из имени владельца и клички питомца.

        class Person
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
        }

        class Pet
        {
            public string Name { get; set; }
            public Person Owner { get; set; }
        }

        /// <summary> 
        /// Simple inner join. 
        /// </summary> 
        public static void InnerJoinExample()
        {
            Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
            Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
            Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
            Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
            Person rui = new Person { FirstName = "Rui", LastName = "Raposo" };

            Pet barley = new Pet { Name = "Barley", Owner = terry };
            Pet boots = new Pet { Name = "Boots", Owner = terry };
            Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
            Pet bluemoon = new Pet { Name = "Blue Moon", Owner = rui };
            Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

            // Create two lists.
            List<Person> people = new List<Person> { magnus, terry, charlotte, arlene, rui };
            List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

            // Create a collection of person-pet pairs. Each element in the collection 
            // is an anonymous type containing both the person's name and their pet's name. 
            var query = from person in people
                        join pet in pets on person equals pet.Owner
                        select new { OwnerName = person.FirstName, PetName = pet.Name };

            foreach (var ownerAndPet in query)
            {
                Console.WriteLine("\"{0}\" is owned by {1}", ownerAndPet.PetName, ownerAndPet.OwnerName);
            }
        }

        // This code produces the following output: 
        // 
        // "Daisy" is owned by Magnus
        // "Barley" is owned by Terry
        // "Boots" is owned by Terry
        // "Whiskers" is owned by Charlotte
        // "Blue Moon" is owned by Rui

Обратите внимание, что объект Person,свойство которого LastName имеет значение "Huff", не отображается среди результатов, так как не существует объекта Pet, свойство Pet.Owner которого было бы равно свойству Person.

Пример соединения с использованием составного ключа

Вместо сопоставления элементов на основании всего лишь одного свойства можно использовать составной ключ для сопоставления элементов на основании нескольких свойств. Для этого укажите, чтобы функция выбора ключа каждой коллекции возвращала анонимный тип, состоящий из свойств, которые необходимо сравнить. Если задается метка свойства, необходимо указать одну и ту же метку во всех анонимных типах ключа. Свойства также должны располагаться в том же порядке.

В следующем примере списки объектов Employee и Student используются для определения того, какие работники являются также студентами. Оба этих типа обладают свойствами FirstName и LastName типа String. Функция, создающая ключи соединения из элементов каждого списка, возвращает анонимный тип, состоящий из свойств FirstName и LastName каждого элемента. Операция соединения сравнивает данные составные ключи на признак равенства и возвращает пары объектов из обоих списков, у которых совпадают имя и фамилия.

        class Employee
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public int EmployeeID { get; set; }
        }

        class Student
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public int StudentID { get; set; }
        }

        /// <summary> 
        /// Performs a join operation using a composite key. 
        /// </summary> 
        public static void CompositeKeyJoinExample()
        {
            // Create a list of employees.
            List<Employee> employees = new List<Employee> {
                new Employee { FirstName = "Terry", LastName = "Adams", EmployeeID = 522459 },
                 new Employee { FirstName = "Charlotte", LastName = "Weiss", EmployeeID = 204467 },
                 new Employee { FirstName = "Magnus", LastName = "Hedland", EmployeeID = 866200 },
                 new Employee { FirstName = "Vernette", LastName = "Price", EmployeeID = 437139 } };

            // Create a list of students.
            List<Student> students = new List<Student> {
                new Student { FirstName = "Vernette", LastName = "Price", StudentID = 9562 },
                new Student { FirstName = "Terry", LastName = "Earls", StudentID = 9870 },
                new Student { FirstName = "Terry", LastName = "Adams", StudentID = 9913 } };

            // Join the two data sources based on a composite key consisting of first and last name, 
            // to determine which employees are also students.
            IEnumerable<string> query = from employee in employees
                                        join student in students
                                        on new { employee.FirstName, employee.LastName }
                                        equals new { student.FirstName, student.LastName }
                                        select employee.FirstName + " " + employee.LastName;

            Console.WriteLine("The following people are both employees and students:");
            foreach (string name in query)
                Console.WriteLine(name);
        }

        // This code produces the following output: 
        // 
        // The following people are both employees and students: 
        // Terry Adams 
        // Vernette Price

Пример множественного соединения

Операции соединения в любом количестве могут быть добавлены друг к другу для выполнения группового соединения. Каждое предложение join в C# сопоставляет указанный источник данных с результатами предыдущего соединения.

В следующем примере создаются три коллекции: список объектов Person, список объектов Cat и список объектов Dog.

Первое предложение join в C# проверяет соответствие людей и кошек на основании сопоставления объекта Person со свойством Cat.Owner. Возвращается последовательность анонимных типов, содержащая объекты Person и свойства Cat.Name.

Второе предложение join в C# сопоставляет анонимные типы, возвращенные первым соединением, с объектами Dog в предоставленном списке собак на основании составного ключа, состоящего из свойства Owner типа Person, и первой буквы клички животного. Возвращается последовательность анонимных типов, содержащая свойства Cat.Name и Dog.Name каждой совпадающей пары. Так как это внутреннее соединение, возвращаются только объекты из первого источника данных, для которых найдено соответствии во втором источнике данных.

        class Person
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
        }

        class Pet
        {
            public string Name { get; set; }
            public Person Owner { get; set; }
        }

        class Cat : Pet
        { }

        class Dog : Pet
        { }

        public static void MultipleJoinExample()
        {
            Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
            Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
            Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
            Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
            Person rui = new Person { FirstName = "Rui", LastName = "Raposo" };
            Person phyllis = new Person { FirstName = "Phyllis", LastName = "Harris" };

            Cat barley = new Cat { Name = "Barley", Owner = terry };
            Cat boots = new Cat { Name = "Boots", Owner = terry };
            Cat whiskers = new Cat { Name = "Whiskers", Owner = charlotte };
            Cat bluemoon = new Cat { Name = "Blue Moon", Owner = rui };
            Cat daisy = new Cat { Name = "Daisy", Owner = magnus };

            Dog fourwheeldrive = new Dog { Name = "Four Wheel Drive", Owner = phyllis };
            Dog duke = new Dog { Name = "Duke", Owner = magnus };
            Dog denim = new Dog { Name = "Denim", Owner = terry };
            Dog wiley = new Dog { Name = "Wiley", Owner = charlotte };
            Dog snoopy = new Dog { Name = "Snoopy", Owner = rui };
            Dog snickers = new Dog { Name = "Snickers", Owner = arlene };

            // Create three lists.
            List<Person> people =
                new List<Person> { magnus, terry, charlotte, arlene, rui, phyllis };
            List<Cat> cats =
                new List<Cat> { barley, boots, whiskers, bluemoon, daisy };
            List<Dog> dogs =
                new List<Dog> { fourwheeldrive, duke, denim, wiley, snoopy, snickers };

            // The first join matches Person and Cat.Owner from the list of people and 
            // cats, based on a common Person. The second join matches dogs whose names start 
            // with the same letter as the cats that have the same owner. 
            var query = from person in people
                        join cat in cats on person equals cat.Owner
                        join dog in dogs on  
                        new { Owner = person, Letter = cat.Name.Substring(0, 1) }
                        equals new { dog.Owner, Letter = dog.Name.Substring(0, 1) }
                        select new { CatName = cat.Name, DogName = dog.Name };

            foreach (var obj in query)
            {
                Console.WriteLine(
                    "The cat \"{0}\" shares a house, and the first letter of their name, with \"{1}\".", 
                    obj.CatName, obj.DogName);
            }
        }

        // This code produces the following output: 
        // 
        // The cat "Daisy" shares a house, and the first letter of their name, with "Duke".
        // The cat "Whiskers" shares a house, and the first letter of their name, with "Wiley".

Пример внутреннего соединения, реализуемого с помощью группового соединения

В следующем примере показано, как реализовать внутреннее соединение с помощью группового соединения.

В query1 выполняется групповое соединение списка объектов Person со списком объектов Pet на основании сопоставления объекта Person и свойства Pet.Owner. Групповое соединение создает коллекцию промежуточных групп, в которой каждая группа состоит из объекта Person и последовательности совпадающих объектов Pet.

При добавлении в запрос второго предложения from данная последовательность из последовательностей объединяется (или выравнивается) в одну более длинную последовательность. Тип элементов конечной последовательности задается предложением select. В этом примере тип является анонимным типом, состоящим из свойств Person.FirstName и Pet.Name каждой соответствующей пары.

Результат query1 аналогичен результирующему набору, полученному при использовании предложения join для выполнения внутреннего соединения без предложения into. Переменная query2 демонстрирует этот эквивалентный запрос.

        class Person
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
        }

        class Pet
        {
            public string Name { get; set; }
            public Person Owner { get; set; }
        }

        /// <summary> 
        /// Performs an inner join by using GroupJoin(). 
        /// </summary> 
        public static void InnerGroupJoinExample()
        {
            Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
            Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
            Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
            Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };

            Pet barley = new Pet { Name = "Barley", Owner = terry };
            Pet boots = new Pet { Name = "Boots", Owner = terry };
            Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte };
            Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry };
            Pet daisy = new Pet { Name = "Daisy", Owner = magnus };

            // Create two lists.
            List<Person> people = new List<Person> { magnus, terry, charlotte, arlene };
            List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy };

            var query1 = from person in people
                         join pet in pets on person equals pet.Owner into gj
                         from subpet in gj
                         select new { OwnerName = person.FirstName, PetName = subpet.Name };

            Console.WriteLine("Inner join using GroupJoin():");
            foreach (var v in query1)
            {
                Console.WriteLine("{0} - {1}", v.OwnerName, v.PetName);
            }

            var query2 = from person in people
                         join pet in pets on person equals pet.Owner
                         select new { OwnerName = person.FirstName, PetName = pet.Name };

            Console.WriteLine("\nThe equivalent operation using Join():");
            foreach (var v in query2)
                Console.WriteLine("{0} - {1}", v.OwnerName, v.PetName);
        }

        // This code produces the following output: 
        // 
        // Inner join using GroupJoin(): 
        // Magnus - Daisy 
        // Terry - Barley 
        // Terry - Boots 
        // Terry - Blue Moon 
        // Charlotte - Whiskers 
        // 
        // The equivalent operation using Join(): 
        // Magnus - Daisy 
        // Terry - Barley 
        // Terry - Boots 
        // Terry - Blue Moon 
        // Charlotte - Whiskers

Компиляция кода

  • Создайте в Visual Studio новый проект консольного приложения.

  • Добавьте ссылку на System.Core.dll, если она отсутствует.

  • Включите пространство имен System.Linq.

  • Скопируйте код из примера в файл program.cs ниже метода Main. Добавьте в метод Main строку кода для вызова вставленного метода.

  • Запустите программу.

См. также

Задачи

Практическое руководство. Выполнение групповых соединений (Руководство по программированию на C#)

Практическое руководство. Выполнение левых внешних соединений (Руководство по программированию на C#)

Как объединить две коллекции (C#) (LINQ to XML)

Ссылки

Join

GroupJoin

Анонимные типы (Руководство по программированию в C#)

Основные понятия

Операции соединения

Анонимные типы (Visual Basic)