Практическое руководство. Заполнение коллекций объектов из нескольких источников (LINQ)

Обновлен: Июль 2008

В этом примере показано объединение данных из различных исходных типов в последовательность новых типов. Примеры в следующем коде соединяют строки с массивами целых чисел. Те же принципы применимы к любой паре источников данных, включая любые сочетания объектов в памяти (включая результаты запросов LINQ to SQL, наборы данных ADO.NET и XML-документы).

Bb513866.alert_note(ru-ru,VS.90).gifПримечание.

Не пытайтесь объединить данные в памяти или файловой системе с данными, все еще находящимися в базе данных. Такие междоменные соединения могут выдавать неопределенный результат из-за различий в способах определения операций соединения для запросов к базам данных и другим типам источников. Кроме того, есть риск, что такая операция может вызвать исключение нехватки памяти, если объем данных в базе данных достаточно велик. Чтобы соединить данные из базы данных с данными в памяти, сначала вызовите ToList или ToArray в запросе к базе данных, а затем выполните соединение с возвращенной коллекцией.

Создание файла данных

Пример

В следующем примере показано использование именованного типа Student для хранения объединенных данных из двух коллекций строк в памяти, которые имитируют данные электронной таблицы в формате CSV. Первый набор строк представляет имена и идентификаторы студентов, а вторая коллекция представляет идентификатор студента (в первом столбце) и четыре результата экзаменов.

Class Student
    Public FirstName As String
    Public LastName As String
    Public ID As Integer
    Public ExamScores As List(Of Integer)
End Class

Class PopulateCollections

    Shared Sub Main()

        ' Join content from spreadsheets into a list of Student objectss.
        ' names.csv contains the student name
        ' plus an ID number. scores.csv contains the ID and a 
        ' set of four test scores. The following query joins
        ' the scores to the student names by using ID as a
        ' matching key, and then projects the results into a new type.

        Dim names As String() = System.IO.File.ReadAllLines("../../../names.csv")
        Dim scores As String() = System.IO.File.ReadAllLines("../../../scores.csv")

        ' Name:    Last[0],       First[1],  ID[2],     Grade Level[3]
        '          Omelchenko,    Svetlana,  111,       2
        ' Score:   StudentID[0],  Exam1[1]   Exam2[2],  Exam3[3],  Exam4[4]
        '          111,           97,        92,        81,        60

        ' This query joins two dissimilar spreadsheets based on common ID value.
        ' Multiple from clauses are used instead of a join clause
        ' in order to store results of id.Split.
        ' Note the dynamic creation of a list of ints for the
        ' TestScores member. We skip 1 because the first string
        ' in the array is the student ID, not an exam score.
        Dim scoreQuery1 = From name In names _
                         Let n = name.Split(New Char() {","}) _
                         From id In scores _
                         Let s = id.Split(New Char() {","}) _
                         Where n(2) = s(0) _
                         Select New Student() _
                         With {.FirstName = n(0), .LastName = n(1), .ID = n(2), _
                               .ExamScores = (From scoreAsText In s Skip 1 _
                                             Select Convert.ToInt32(scoreAsText)).ToList()}

        ' Optional. Store the query results for faster access
        ' in future queries. May be useful with very large data files.
        Dim students As List(Of Student) = scoreQuery1.ToList()

        ' Display the list contents
        ' and perform a further calculation
        For Each s In students
            Console.WriteLine("The average score of " & s.FirstName & " " & _
                              s.LastName & " is " & s.ExamScores.Average())
        Next

        ' Keep console window open in debug mode.
        Console.WriteLine("Press any key to exit.")
        Console.ReadKey()
    End Sub
End Class
' Output: 
'The average score of Adams Terry is 85.25
'The average score of Fakhouri Fadi is 92.25
'The average score of Feng Hanying is 88
'The average score of Garcia Cesar is 88.25
'The average score of Garcia Debra is 67
'The average score of Garcia Hugo is 85.75
'The average score of Mortensen Sven is 84.5
'The average score of O'Donnell Claire is 72.25
'The average score of Omelchenko Svetlana is 82.5
'The average score of Tucker Lance is 81.75
'The average score of Tucker Michael is 92
'The average score of Zabokritski Eugene is 83
class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int ID { get; set; }
    public List<int> ExamScores { get; set; }
}

class PopulateCollections
{
    static void Main()
    {
        // These data files are defined in How to: Join Content from Dissimilar Files (LINQ) 
        string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
        string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

        // Merge the data sources using a named type.
        // var could be used instead of an explicit type.
        // Note the dynamic creation of a list of ints for the
        // TestScores member. We skip 1 because the first string
        // in the array is the student ID, not an exam score.
        IEnumerable<Student> queryNamesScores =
            from name in names
            let x = name.Split(',')
            from score in scores
            let s = score.Split(',')
            where x[2] == s[0]
            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. Could be useful with
        // very large data files.
        List<Student> students = queryNamesScores.ToList();

        // Display the results and perform one further calculation.
        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();
    }
}
/* Output: 
    The average score of Adams Terry is 85.25.
    The average score of Fakhouri Fadi is 92.25.
    The average score of Feng Hanying is 88.
    The average score of Garcia Cesar is 88.25.
    The average score of Garcia Debra is 67.
    The average score of Garcia Hugo is 85.75.
    The average score of Mortensen Sven is 84.5.
    The average score of O'Donnell Claire is 72.25.
    The average score of Omelchenko Svetlana is 82.5.
    The average score of Tucker Lance is 81.75.
    The average score of Tucker Michael is 92.
    The average score of Zabokritski Eugene is 83.
 */

Источники данных в этих примерах инициализируются с помощью инициализаторов объектов. Запрос использует предложение join для сопоставления имен с результатами. ID используется в качестве внешнего ключа. Однако в одном источнике идентификатор является строкой, а в другом источнике целым числом. Поскольку join требует сравнения на равенство, сначала необходимо извлечь идентификатор из строки и преобразовать его в целое число. Это выполняется в двух предложениях let. Временный идентификатор x в первом предложении let хранит массив из трех строк, созданных разделением исходной строки на каждом пробеле. Идентификатор n во втором предложении let хранит результат преобразования подстроки идентификатора в целое число. В предложении select инициализатор объектов используется для создания каждого нового объекта Student, применяя данные из двух источников.

Если нет необходимости хранить результаты запроса, анонимные типы могут быть более удобными, чем именованные типы. Именованные типы необходимы, если результаты запроса передаются за пределы метода, в котором выполняется запрос. В следующем примере выполняется та же задача, что и в предыдущем примере, но с использованием анонимных типов вместо именованных:

' This query uses an anonymous type
' Note the dynamic creation of a list of ints for the
' TestScores member. We skip 1 because the first string
' in the array is the student ID, not an exam score.
Dim scoreQuery2 = From name In names _
                 Let n = name.Split(New Char() {","}) _
                 From id In scores _
                 Let s = id.Split(New Char() {","}) _
                 Where n(2) = s(0) _
                 Select New With {.Last = n(0), _
                                  .First = n(1), _
                                  .TestScores = (From scoreAsText In s Skip 1 _
                                     Select Convert.ToInt32(scoreAsText)).ToList()}

' Display the list contents
' and perform a further calculation
For Each s In scoreQuery2
    Console.WriteLine("The average score of " & s.First & " " & s.Last & " is " & s.TestScores.Average())
Next
// Merge the data sources by using an anonymous type.
// Note the dynamic creation of a list of ints for the
// TestScores member. We skip 1 because the first string
// in the array is the student ID, not an exam score.
var queryNamesScores2 =
    from name in names
    let x = name.Split(',')
    from score in scores
    let s = score.Split(',')
    where x[2] == s[0]
    select new 
    {
        First = x[0],
        Last = x[1],
        TestScores = (from scoreAsText in s.Skip(1)
                      select Convert.ToInt32(scoreAsText))
                      .ToList()
    };

// Display the results and perform one further calculation.
foreach (var student in queryNamesScores2)
{
    Console.WriteLine("The average score of {0} {1} is {2}.",
        student.First, student.Last, student.TestScores.Average());
}

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

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

  • Скопируйте этот код в проект.

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

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

См. также

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

LINQ и строки

Ссылки

Инициализаторы объектов и коллекций (Руководство по программированию в C#)

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

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

Дата

Журнал

Причина

Июль 2008

Добавлен второй набор примеров кода.

Исправление ошибки содержимого.