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

Запрос представляет собой выражение, извлекающее данные из источника данных.A query is an expression that retrieves data from a data source. Запросы обычно выражаются на специализированном языке запросов.Queries are usually expressed in a specialized query language. Со временем для различных типов источников данных, например SQL для реляционных баз данных и XQuery для XML, были разработаны разные языки.Different languages have been developed over time for the various types of data sources, for example SQL for relational databases and XQuery for XML. Поэтому разработчикам приходится учить новый язык запросов для каждого типа источника данных или формата данных, для которых они должны обеспечить поддержку.Therefore, developers have had to learn a new query language for each type of data source or data format that they must support. LINQ упрощает ситуацию, реализуя согласованную модель работы с данными для различных типов источников и форматов данных.LINQ simplifies this situation by offering a consistent model for working with data across various kinds of data sources and formats. В запросе LINQ вы всегда работаете с объектами.In a LINQ query, you are always working with objects. Вы используете одинаковые базовые шаблоны кода для запроса и преобразования данных в XML-документы, базы данных SQL, наборы данных ADO.NET, коллекции .NET и любые другие форматы, для которых доступен поставщик LINQ.You use the same basic coding patterns to query and transform data in XML documents, SQL databases, ADO.NET Datasets, .NET collections, and any other format for which a LINQ provider is available.

Три составляющие операции запросаThree Parts of a Query Operation

Все операции запросов LINQ состоят из трех отдельных действий:All LINQ query operations consist of three distinct actions:

  1. получение источника данных;Obtain the data source.

  2. создание запроса;Create the query.

  3. выполнение запроса.Execute the query.

В следующем примере показано, как эти три части операции запроса выражаются в исходном коде.The following example shows how the three parts of a query operation are expressed in source code. В этом примере для удобства используется массив целых чисел в качестве источника данных. Тем не менее те же принципы применимы к другим источникам данных.The example uses an integer array as a data source for convenience; however, the same concepts apply to other data sources also. Остальные процедуры этого раздела содержат ссылки на этот пример.This example is referred to throughout the rest of this topic.

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);
        }
    }
}

На следующем рисунке показана полная операция запроса.The following illustration shows the complete query operation. Выполнение запроса в LINQ отличается от самого запроса.In LINQ, the execution of the query is distinct from the query itself. Иными словами, данные не извлекаются путем создания переменной запроса.In other words, you have not retrieved any data just by creating a query variable.

Схема завершенной операции запроса LINQ.

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

Так как источник данных в предыдущем примере является массивом, он неявно поддерживает универсальный интерфейс IEnumerable<T>.In the previous example, because the data source is an array, it implicitly supports the generic IEnumerable<T> interface. Это значит, что его можно запросить с помощью LINQ.This fact means it can be queried with LINQ. Запрос выполняется в операторе foreach, и для foreach требуется IEnumerable или IEnumerable<T>.A query is executed in a foreach statement, and foreach requires IEnumerable or IEnumerable<T>. Типы, которые поддерживают IEnumerable<T> или производный интерфейс, например универсальный интерфейс IQueryable<T>, называются запрашиваемыми типами.Types that support IEnumerable<T> or a derived interface such as the generic IQueryable<T> are called queryable types.

Запрашиваемый тип не требует внесения изменений или специальной обработки, чтобы служить источником данных LINQ.A queryable type requires no modification or special treatment to serve as a LINQ data source. Если источник данных не находится в памяти в качестве запрашиваемого типа, поставщик LINQ должен представить его как таковой.If the source data is not already in memory as a queryable type, the LINQ provider must represent it as such. Например, LINQ to XMLLINQ to XML загружает XML-документ в запрашиваемый тип XElement:For example, LINQ to XMLLINQ to XML loads an XML document into a queryable XElement type:

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

В LINQ to SQLLINQ to SQL сначала необходимо создать объектно-реляционное сопоставление во время разработки вручную либо с помощью инструментов LINQ to SQL в Visual Studio.With LINQ to SQLLINQ to SQL, you first create an object-relational mapping at design time either manually or by using the LINQ to SQL Tools in Visual Studio. Вы создаете запросы к объектам, а LINQ to SQLLINQ to SQL во время выполнения обрабатывает взаимодействие с базой данных.You write your queries against the objects, and at run-time LINQ to SQLLINQ to SQL handles the communication with the database. В следующем примере Customers представляет собой определенную таблицу в базе данных, а тип результата запроса (IQueryable<T>) является производным от IEnumerable<T>.In the following example, Customers represents a specific table in the database, and the type of the query result, IQueryable<T>, derives from 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.For more information about how to create specific types of data sources, see the documentation for the various LINQ providers. Но общее правило очень простое: источником данных LINQ является любой объект, который поддерживает универсальный интерфейс IEnumerable<T> или интерфейс, наследуемый от него.However, the basic rule is very simple: a LINQ data source is any object that supports the generic IEnumerable<T> interface, or an interface that inherits from it.

Примечание

Такие типы как ArrayList, которые поддерживают неуниверсальный интерфейс IEnumerable, также можно использовать в качестве источника данных LINQ.Types such as ArrayList that support the non-generic IEnumerable interface can also be used as a LINQ data source. Дополнительные сведения см. в разделе Практическое руководство. Выполнение запроса к ArrayList с помощью LINQ (C#).For more information, see How to query an ArrayList with LINQ (C#).

ЗапросThe Query

Запрос указывает, какую информацию нужно извлечь из источника или источников данных.The query specifies what information to retrieve from the data source or sources. Дополнительно в запросе можно указать, как следует сортировать, группировать и формировать возвращаемую информацию.Optionally, a query also specifies how that information should be sorted, grouped, and shaped before it is returned. Запрос хранится в переменной запроса и инициализируется выражением запроса.A query is stored in a query variable and initialized with a query expression. Чтобы упростить написание запросов, в C# был представлен новый синтаксис запроса.To make it easier to write queries, C# has introduced new query syntax.

В предыдущем примере запрос возвращает все четные числа из массива целых чисел.The query in the previous example returns all the even numbers from the integer array. Выражение запроса содержит три предложения: from, where и select.The query expression contains three clauses: from, where and select. (Если вы знакомы с SQL, то должны были заметить, что порядок предложений противоположен порядку в SQL.) Предложение from указывает источник данных, предложение where применяет фильтр, а предложение select задает тип возвращаемых элементов.(If you are familiar with SQL, you will have noticed that the ordering of the clauses is reversed from the order in SQL.) The from clause specifies the data source, the where clause applies the filter, and the select clause specifies the type of the returned elements. Эти и другие предложения запросов подробно описываются в разделе LINQ.These and the other query clauses are discussed in detail in the Language Integrated Query (LINQ) section. А сейчас важно то, что в LINQ сама переменная запроса не выполняет никаких действий и не возвращает никаких данных.For now, the important point is that in LINQ, the query variable itself takes no action and returns no data. Она просто хранит сведения, необходимые для предоставления результатов при выполнении запроса на более позднем этапе.It just stores the information that is required to produce the results when the query is executed at some later point. Дополнительные сведения о принципах конструирования запросов см. в разделе Общие сведения о стандартных операторах запросов (C#).For more information about how queries are constructed behind the scenes, see Standard Query Operators Overview (C#).

Примечание

Запросы могут также выражаться с помощью синтаксиса методов.Queries can also be expressed by using method syntax. Дополнительные сведения см. в разделе Синтаксис запросов и синтаксис методов в LINQ.For more information, see Query Syntax and Method Syntax in LINQ.

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

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

Как уже говорилось ранее, сама переменная запроса хранит команды запроса.As stated previously, the query variable itself only stores the query commands. Фактическое выполнение запроса откладывается до тех пор, пока переменная запроса не будет обработана в операторе foreach.The actual execution of the query is deferred until you iterate over the query variable in a foreach statement. Эта концепция называется отложенным выполнением и демонстрируется в следующем примере:This concept is referred to as deferred execution and is demonstrated in the following example:

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

Оператор foreach также является частью кода, в которой извлекаются результаты запроса.The foreach statement is also where the query results are retrieved. Например, в предыдущем запросе переменная итерации num содержит каждое значение (по одному за раз) в возвращенной последовательности.For example, in the previous query, the iteration variable num holds each value (one at a time) in the returned sequence.

Поскольку сама переменная запроса никогда не содержит результаты запроса, вы можете выполнять ее так часто, как это необходимо.Because the query variable itself never holds the query results, you can execute it as often as you like. Например, может иметься база данных, постоянно обновляемая отдельным приложением.For example, you may have a database that is being updated continually by a separate application. В приложении можно создать один запрос, получающий самые последние данные, и регулярно выполнять его с некоторым интервалом, каждый раз получая разные результаты.In your application, you could create one query that retrieves the latest data, and you could execute it repeatedly at some interval to retrieve different results every time.

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

Запросы, выполняющие статистические функции для диапазона исходных элементов, сначала должны выполнить итерацию по этим элементам.Queries that perform aggregation functions over a range of source elements must first iterate over those elements. Примерами таких запросов являются Count, Max, Average и First.Examples of such queries are Count, Max, Average, and First. Они выполняются без явного оператора foreach, поскольку сам запрос должен использовать foreach для возвращения результата.These execute without an explicit foreach statement because the query itself must use foreach in order to return a result. Обратите внимание, что такие типы запросов возвращают одно значение, а не коллекцию IEnumerable.Note also that these types of queries return a single value, not an IEnumerable collection. Следующий запрос возвращает количество четных чисел в исходном массиве:The following query returns a count of the even numbers in the source array:

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

int evenNumCount = evenNumQuery.Count();

Чтобы принудительно вызвать немедленное выполнение любого запроса и кэшировать его результаты, вы можете вызвать методы ToList или ToArray.To force immediate execution of any query and cache its results, you can call the ToList or ToArray methods.

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 сразу после выражения запроса.You can also force execution by putting the foreach loop immediately after the query expression. При этом путем вызова ToList или ToArray можно также кэшировать все данные в одном объекте коллекции.However, by calling ToList or ToArray you also cache all the data in a single collection object.

См. такжеSee also