Введение в запросы LINQ (C#)

Запрос представляет собой выражение, извлекающее данные из источника данных.Запросы обычно выражаются на специальном языке запросов.Со временем были разработаны различные языки для различных типов источников данных, например SQL для реляционных баз данных и XQuery для XML.Таким образом, разработчики вынуждены изучать новый язык запросов для каждого типа источника данных или формата данных, который они должны поддерживать.LINQ упрощает ситуацию, предлагая единообразную модель для работы с данными в различных видах источников и форматов данных.В запросе LINQ работа всегда осуществляется с объектами.Для запросов и преобразований данных в XML-документах, базах данных SQL, наборах данных ADO.NET, коллекциях .NET и любых других форматах, для которых доступен поставщик LINQ, используются одинаковые базовые шаблоны кодирования.

Три части операции запроса

Все операции запроса LINQ состоят из трех различных действий.

  1. Получение источника данных.

  2. Создание запроса.

  3. Выполнение запроса.

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

class IntroToLINQ
{        
    static void Main()
    {
        // The Three Parts of a LINQ Query:
        //  1. Data source.
        int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

        // 2. Query creation.
        // numQuery is an IEnumerable<int>
        var numQuery =
            from num in numbers
            where (num % 2) == 0
            select num;

        // 3. Query execution.
        foreach (int num in numQuery)
        {
            Console.Write("{0,1} ", num);
        }
    }
}

На следующем рисунке показана завершенная операция запроса.В LINQ выполнение запроса отличается от самого запроса; другими словами, создание переменной запроса само по себе не связано с получением данных.

Завершенная операция запроса LINQ

Источник данных

В предыдущем примере источником данных является массив, поэтому он неявно поддерживает универсальный интерфейс IEnumerable<T>.Это значит, что к нему можно выполнять запросы с LINQ.Запрос выполняется в инструкцию foreach, и foreach для IEnumerable или IEnumerable<T>. Типы, которые поддерживают IEnumerable<T> или производный интерфейс как универсальный шаблон IQueryable<T> вызываются запрашиваемыми типами.

Для запрашиваемого типа, выступающего в качестве источника данных LINQ, не требуются изменения или специальная обработка.Если источник данных еще не находится в памяти в виде запрашиваемого типа, поставщик LINQ должен представить его как таковой.Например, LINQ to XML загружает XML-документ в запрашиваемый тип XElement.

// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");

Используя LINQ to SQL, сначала создайте объектно-реляционное сопоставление в режиме разработки вручную либо с помощью Object Relational Designer (O/R Designer).Напишите запросы к объектам, а во время выполнения LINQ to SQL будет осуществлять взаимодействие с базой данных.В следующем примере Customers представляет определенную таблицу в базе данных, а тип результата запроса IQueryable<T> наследуется от IEnumerable<T>.

Northwnd db = new Northwnd(@"c:\northwnd.mdf");

// Query for customers in London.
IQueryable<Customer> custQuery =
    from cust in db.Customers
    where cust.City == "London"
    select cust;

Для получения дополнительных сведений о способах создания определенных типов источников данных см. документацию по различным поставщикам LINQ.Основное правило является очень простым: источник данных LINQ — это любой объект, поддерживающий универсальный интерфейс IEnumerable<T> или интерфейс, наследуемый от него.

ПримечаниеПримечание

Такие типы, как ArrayList, поддерживающие неуниверсальный интерфейс IEnumerable, также могут использоваться в качестве источников данных LINQ.Дополнительные сведения см. в разделе Практическое руководство. Выполнение запроса к ArrayList с помощью LINQ.

Запрос

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

Запрос из предыдущего примера возвращает все четные числа из массива целых чисел.Выражение запроса содержит три предложения: from, where и select.(Если вы знакомы с SQL, обратите внимание, что порядок предложений противоположен порядку в SQL.) Предложение from указывает источник данных, предложение where применяет фильтр, а предложение select указывает тип возвращаемых элементов.Эти и другие предложения запросов подробно описаны в разделе Выражения запросов LINQ (Руководство по программированию на C#).Важно, что в LINQ сама переменная запроса не предпринимает действий и не возвращает никаких данных.Она просто хранит сведения, необходимые для предоставления результатов при последующем выполнении запроса.Дополнительные сведения о построении запросов см. в разделе Общие сведения о стандартных операторах запроса.

ПримечаниеПримечание

Запросы могут также выражаться с помощью синтаксиса методов.Дополнительные сведения см. в разделе Синтаксис запросов и синтаксис методов в LINQ (C#).

Выполнение запроса

Bb397906.collapse_all(ru-ru,VS.110).gifОтложенное выполнение

Как уже говорилось ранее, сама переменная запроса только хранит команды запроса.Фактическое выполнение запроса откладывается до выполнения итерации переменной запроса в операторе foreach.Эту концепцию называют отложенным выполнением, она показана в следующем примере.

//  Query execution. 
foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

Оператор foreach является также местом, где извлекаются результаты запроса.Например, в предыдущем запросе переменная итерации num содержит каждое (по очереди) значение в возвращаемой последовательности.

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

Bb397906.collapse_all(ru-ru,VS.110).gifПринудительное немедленное выполнение

Запросы, выполняющие статистические функции над диапазоном исходных элементов, должны сначала выполнить итерацию этих элементов.Примерами таких запросов являются Count, Max, Average и First.Они выполняются без явного оператора foreach, поскольку сам запрос должен использовать foreach для возвращения результата.Обратите внимание, что такой тип запросов возвращает одиночное значение, а не коллекцию IEnumerable.Следующий запрос возвращает количество четных чисел в исходном массиве.

var evenNumQuery = 
    from num in numbers
    where (num % 2) == 0
    select num;

int evenNumCount = evenNumQuery.Count();

Чтобы принудительно вызвать немедленное выполнение любого запроса и кэшировать его результаты, можно вызвать метод ToList<TSource> или ToArray<TSource>.

List<int> numQuery2 =
    (from num in numbers
     where (num % 2) == 0
     select num).ToList();

// or like this:
// numQuery3 is still an int[]

var numQuery3 =
    (from num in numbers
     where (num % 2) == 0
     select num).ToArray();

Можно также принудительно выполнить запрос, поместив цикл foreach сразу после выражения запроса.Однако вызов ToList или ToArray также кэширует все данные в одной коллекции объектов.

См. также

Задачи

Пошаговое руководство. Написание запросов на C# (LINQ)

Примеры LINQ

Ссылки

foreach, in (Справочник по C#)

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

O/R Designer Overview

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

Другие ресурсы

Приступая к работе с LINQ в C#

Ключевые слова запроса (Справочник по C#)

LINQ и видеозаписи отложенного выполнения