Введение в запросы LINQ

Обновлен: Ноябрь 2007

Запрос представляет собой выражение, получающее данные из источника данных. Запросы обычно выражаются на специальном языке запросов. Со временем были разработаны различные языки для различных типов источников данных, например 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, сначала создайте объектно-реляционное сопоставление в режиме разработки вручную либо с помощью Реляционный конструктор объектов. Напишите запросы к объектам, а во время выполнения LINQ to SQL будет осуществлять взаимодействие с базой данных. В следующем примере Customer представляет определенную таблицу в базе данных, а Table<Customer> поддерживает универсальный IQueryable<T>, производный от IEnumerable<T>.

// Create a data source from a SQL Server database.
// using System.Data.Linq;
DataContext db = new DataContext(@"c:\northwind\northwnd.mdf");

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

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

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

Запрос

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

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

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

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

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

Отложенное выполнение

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

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

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

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

Принудительное немедленное выполнение

Запросы, выполняющие статистические функции над диапазоном исходных элементов, должны сначала выполнить итерацию этих элементов. Примерами таких запросов являются 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 также кэширует все данные в одной коллекции объектов.

См. также

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

Общие сведения о реляционном конструкторе объектов

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

Ссылки

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

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

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

Примеры LINQ

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

Видео: LINQ и отложенное выполнение