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

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

Однако предложение join нельзя использовать в следующих случаях:

  • Соединение объявлено в выражении неравенства.

  • Соединение объявлено в нескольких выражениях равенства или неравенства.

  • Необходимость создания временной переменной диапазона для правосторонней (внутренней) последовательности перед операцией соединения.

Чтобы независимо представить каждый источник данных, при выполнении соединений, не являющихся уравнивающими, можно использовать несколько предложений from. Затем переменной диапазона для каждого источника можно применить выражение предиката в предложении where. Выражение также может принимать форму вызова метода.

Примечание

Не путайте данный вид пользовательской операции соединения с использованием нескольких предложений from для доступа к внутренним коллекциям.Дополнительные сведения см. в разделе Предложение join (Справочник по C#).

Пример

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

Результатом второго метода является последовательность всех продуктов, идентификатор категории которых находится в списке категорий с правой стороны. Учтите необходимость использование предложения let и метода Contains для создания временного массива. Также можно создать массив перед запросом и удалить первое предложение from.

     class CustomJoins
     {

         #region Data

         class Product
         {
             public string Name { get; set; }
             public int CategoryID { get; set; }
         }

         class Category
         {
             public string Name { get; set; }
             public int ID { get; set; }
         }

         // Specify the first data source.
         List<Category> categories = new List<Category>()
 { 
     new Category(){Name="Beverages", ID=001},
     new Category(){ Name="Condiments", ID=002},
     new Category(){ Name="Vegetables", ID=003},         
 };

         // Specify the second data source.
         List<Product> products = new List<Product>()
{
   new Product{Name="Tea",  CategoryID=001},
   new Product{Name="Mustard", CategoryID=002},
   new Product{Name="Pickles", CategoryID=002},
   new Product{Name="Carrots", CategoryID=003},
   new Product{Name="Bok Choy", CategoryID=003},
   new Product{Name="Peaches", CategoryID=005},
   new Product{Name="Melons", CategoryID=005},
   new Product{Name="Ice Cream", CategoryID=007},
   new Product{Name="Mackerel", CategoryID=012},
 };
         #endregion

         static void Main()
         {
             CustomJoins app = new CustomJoins();
             app.CrossJoin();
             app.NonEquijoin();

             Console.WriteLine("Press any key to exit.");
             Console.ReadKey();
         }

         void CrossJoin()
         {
             var crossJoinQuery =
                 from c in categories
                 from p in products
                 select new { c.ID, p.Name };

             Console.WriteLine("Cross Join Query:");
             foreach (var v in crossJoinQuery)
             {
                 Console.WriteLine("{0,-5}{1}", v.ID, v.Name);
             }
         }

         void NonEquijoin()
         {
             var nonEquijoinQuery =
                 from p in products
                 let catIds = from c in categories
                              select c.ID
                 where catIds.Contains(p.CategoryID) == true
                 select new { Product = p.Name, CategoryID = p.CategoryID };

             Console.WriteLine("Non-equijoin query:");
             foreach (var v in nonEquijoinQuery)
             {
                 Console.WriteLine("{0,-5}{1}", v.CategoryID, v.Product);
             }
         }
     }
     /* Output:
 Cross Join Query:
 1    Tea
 1    Mustard
 1    Pickles
 1    Carrots
 1    Bok Choy
 1    Peaches
 1    Melons
 1    Ice Cream
 1    Mackerel
 2    Tea
 2    Mustard
 2    Pickles
 2    Carrots
 2    Bok Choy
 2    Peaches
 2    Melons
 2    Ice Cream
 2    Mackerel
 3    Tea
 3    Mustard
 3    Pickles
 3    Carrots
 3    Bok Choy
 3    Peaches
 3    Melons
 3    Ice Cream
 3    Mackerel
 Non-equijoin query:
 1    Tea
 2    Mustard
 2    Pickles
 3    Carrots
 3    Bok Choy
 Press any key to exit.
      */

В следующем примере запрос должен соединять две последовательности на основе сопоставления ключей, который в случае с внутренней (правосторонней) последовательностью не может быть получен до предложения соединения. Если соединение было выполнено с предложением join, для каждого элемента требуется вызвать метод Split. Использование нескольких предложений from позволяет запросу избежать издержек, связанных с повторным вызовом метода. Однако поскольку join оптимизировано, в данном случае его использование может быть эффективнее нескольких предложений from. Результаты будут зависеть в основном от затрат на вызов метода.

class MergeTwoCSVFiles
{
    static void Main()
    {
        // See section Compiling the Code for information about the data files.
        string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
        string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

        // Merge the data sources using a named type.
        // You could use var instead of an explicit type for the query.
        IEnumerable<Student> queryNamesScores =
            // Split each line in the data files into an array of strings.
            from name in names
            let x = name.Split(',')
            from score in scores
            let s = score.Split(',')
            // Look for matching IDs from the two data files.
            where x[2] == s[0]
            // If the IDs match, build a Student object.
            select new Student()
            {
                FirstName = x[0],
                LastName = x[1],
                ID = Convert.ToInt32(x[2]),
                ExamScores = (from scoreAsText in s.Skip(1)
                              select Convert.ToInt32(scoreAsText)).
                              ToList()
            };

        // Optional. Store the newly created student objects in memory
        // for faster access in future queries
        List<Student> students = queryNamesScores.ToList();

        foreach (var student in students)
        {
            Console.WriteLine("The average score of {0} {1} is {2}.",
                student.FirstName, student.LastName, student.ExamScores.Average());
        }

        //Keep console window open in debug mode
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}

class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int ID { get; set; }
    public List<int> ExamScores { get; set; }
}

/* Output: 
    The average score of Omelchenko Svetlana is 82.5.
    The average score of O'Donnell Claire is 72.25.
    The average score of Mortensen Sven is 84.5.
    The average score of Garcia Cesar is 88.25.
    The average score of Garcia Debra is 67.
    The average score of Fakhouri Fadi is 92.25.
    The average score of Feng Hanying is 88.
    The average score of Garcia Hugo is 85.75.
    The average score of Tucker Lance is 81.75.
    The average score of Adams Terry is 85.25.
    The average score of Zabokritski Eugene is 83.
    The average score of Tucker Michael is 92.
 */

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

  • Создайте проект консольного приложения Visual Studio, который предназначен для .NET Framework версии 3.5 или более поздней версии. По умолчанию в проекте имеются ссылка на файл System.Core.dll и директива using (C#) для пространства имен System.Linq.

  • Замените класс Program кодом из предыдущего примера.

  • Следуйте инструкциям в Практическое руководство. Объединение содержимого из файлов разных форматов (LINQ) для настройки файлов данных scores.csv и names.csv.

  • Нажмите клавишу F5, чтобы скомпилировать и выполнить программу.

  • Нажмите любую клавишу для выхода из окна консоли.

См. также

Задачи

Практическое руководство. Упорядочение результатов предложения соединения (Руководство по программированию на C#)

Ссылки

Предложение join (Справочник по C#)

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

Выражения запросов LINQ (Руководство по программированию на C#)

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

Журнал изменений

Дата

Журнал

Причина

Август 2010

Изменен второй пример для упрощения выполнения.

Улучшение информации.