C Sharp > Выражения запросов LINQ
23.11.2016 18:26:08
Наиболее часто встречающиеся слова в статье:
[запроса] [FirstName] [LastName] [примере] [Console] [student] [WriteLine] [следующем] [Student] [IEnumerable]
Статья:
LINQ — это название набора технологий, основанных на интеграции возможностей запроса непосредственно в язык C# (а также в Visual Basic и, возможно, в любые другие языки .NET). Благодаря LINQ запрос теперь является одним из основных структурных элементов языка, подобно классам, методам, событиям и т. д.
Для разработчиков, сознающих запросы, наиболее очевидная "встроенная в язык" часть LINQ — это выражение запросов. Выражения запросов составляются в соответствии с декларативным синтаксисом запроса, который был впервые предложен в C# 3.0. Синтаксис запроса позволяет выполнять достаточно сложную фильтрацию, упорядочение и операции группирования при работе с источниками данных, используя минимум программного кода. Можно использовать одинаковые базовые шаблоны запросов для запроса и преобразования данных в базах данных SQL, наборах данных ADO.NET, документах и потоках XML, а также в коллекциях .NET.
В следующем примере показана выполненная операция запроса.
class LINQQueryExpressions { static void Main() { // Specify the data source. int[] scores = new int[] { 97, 92, 81, 60 }; // Define the query expression. IEnumerable<int> scoreQuery = from score in scores where score > 80 select score; // Execute the query. foreach (int i in scoreQuery) { Console.Write(i + " "); } } } // Output: 97 92 81
Запрос — это набор инструкций, которые описывают, какие данные необходимо извлечь из указанного источника (или источников) данных, а также описывают форму и организацию извлекаемых данных. Запрос отличается от полученного с его помощью результата.
Обычно исходные данные логически организованы как последовательность элементов одного вида. База данных SQL содержит последовательность строк. Аналогично, ADO.NET DataTable содержит последовательность объектов DataRow. В файле XML содержится "последовательность" элементов XML (они организованы иерархически в древовидную структуру). Коллекция в памяти содержит последовательность объектов.
С точки зрения приложения определенные тип и структура оригинальных исходных
данных не важны. Исходные данные всегда представляются приложению как коллекция IEnumerable<T> или IQueryable<T>.
В LINQ to XML исходные становятся видимыми какIEnumerable
<XElement>.
В LINQ to DataSet — как IEnumerable
<DataRow>.
В LINQ to SQL — как IEnumerable
или IQueryable
любого
из пользовательских объектов, которые были определены для представления данных в
таблице SQL.
При такой исходной последовательности запрос может выполнять одно из трех возможных действий.
-
Извлечение подмножества элементов для получения новой последовательности без изменения отдельных элементов. Затем запрос может отсортировать или сгруппировать возвращаемую последовательность различными способами, как показано в следующем примере (предположим, что
scores
являетсяint[]
):IEnumerable<int> highScoresQuery = from score in scores where score > 80 orderby score descending select score;
-
Извлечение последовательности элементов, как и в предыдущем примере, но с преобразованием элементов в новый вид объекта. Например, запрос может извлекать только фамилии из определенных записей клиентов в источнике данных. Запрос также может извлекать полную запись и использовать ее для создания другого типа объекта в памяти или даже данных XML перед созданием заключительной последовательности результатов. В следующем примере показана трансформация
int
вstring
. Обратите внимание на новый типhighScoresQuery
.IEnumerable<string> highScoresQuery2 = from score in scores where score > 80 orderby score descending select String.Format("The score is {0}", score);
-
Извлечение одноэлементного значения исходных данных, такого как:
-
Количество элементов, которое соответствует определенному условию.
-
Элемент, обладающий наибольшим или наименьшим значением.
-
Первый элемент, соответствующий условию, или сумма определенных значений в заданном наборе элементов. Например, следующий запрос возвращает количество оценок выше 80 из целочисленного массива
scores
:
int highScoreCount = (from score in scores where score > 80 select score) .Count();
В предыдущем примере обратите внимание на использование скобок вокруг выражения запроса перед вызовом метода
Count
. Его также можно выразить, используя новую переменную для сохранения конкретного результата. Этот метод является более удобочитаемым, так как переменная, в которой хранится запрос, хранится отдельно от запроса, в котором хранится результат.IEnumerable<int> highScoresQuery3 = from score in scores where score > 80 select score; int scoreCount = highScoresQuery3.Count();
-
В предыдущем примере запрос выполняется в вызове Count
,
так как Count
должен
выполнить итерацию результатов, чтобы определить количество элементов,
возвращенных highScoresQuery
.
Выражение запроса — запрос, выраженный с помощью синтаксиса запроса. Выражение запроса является конструкцией языка первого класса. Выражение запроса похоже на любое другое выражение, оно может использоваться в любом контексте, в котором выражение C# является допустимым. Выражение запроса состоит их набора предложений, написанных в декларативном синтаксисе, аналогичном SQL или XQuery. Каждое предложение, в свою очередь, содержит одно или несколько выражений C#, которые могут являться выражениями запроса или могут содержать выражение запроса.
Выражение запроса должно начинаться предложением from и
оканчиваться предложением select или group.
Между первым предложениемfrom
и
последним предложением select
или group
может
содержаться одно или несколько необязательных предложений where, orderby,join, let или
даже дополнительных предложений from.
Также можно использовать ключевое слово into,
чтобы результат предложения join
или group
мог
служить источником дополнительных предложений запроса в том же выражении
запроса.
Переменная запроса
В LINQ переменная запроса — это любая переменная, сохраняющая запрос вместо результатов запроса.
Говоря точнее, переменная запроса всегда является перечислимым типом и
производит последовательность элементов, когда она используется в итерации
оператораforeach
или
прямом вызове ее метода IEnumerator.MoveNext
.
В следующем примере кода показано простое выражение запроса с одним источником
данных, одним предложением фильтрации, одним предложением упорядочения и без
трансформации исходных элементов. Предложение select
завершает
запрос.
static void Main() { // Data source. int[] scores = { 90, 71, 82, 93, 75, 82 }; // Query Expression. IEnumerable<int> scoreQuery = //query variable from score in scores //required where score > 80 // optional orderby score descending // optional select score; //must end with select or group // Execute the query to produce the results foreach (int testScore in scoreQuery) { Console.WriteLine(testScore); } } // Outputs: 93 90 82 82
В предыдущем примере scoreQuery
— переменная
запроса, которую иногда называют
просто запросом.
В переменной запроса не хранятся фактические данные результата, которые
получаются с помощью цикла foreach
.
Когда выполняется оператор foreach
,
результаты запроса не возвращаются с помощью переменной запроса scoreQuery
.
В таком случае они возвращаются с помощью переменной итерацииtestScore
.
Итерация переменно scoreQuery
может
выполняться во втором цикле foreach
.
Результаты будет теми же, если ни они, ни источник данных не изменяются.
В переменной запроса может храниться запрос, выраженный с помощью синтаксиса
запроса или метода запроса, или их комбинации. В следующих примерах queryMajorCities
и queryMajorCities2
являются
переменными запроса.
//Query syntax IEnumerable<City> queryMajorCities = from city in cities where city.Population > 100000 select city; // Method-based syntax IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);
С другой стороны, в следующих примерах показаны переменные, которые не являются переменными запроса даже несмотря на то, что все они инициализируются запросом. Они не являются переменными запроса, так как в них хранятся результаты запроса.
int highestScore = (from score in scores select score) .Max(); // or split the expression IEnumerable<int> scoreQuery = from score in scores select score; int highScore = scoreQuery.Max(); List<City> largeCitiesList = (from country in countries from city in country.Cities where city.Population > 10000 select city) .ToList(); // or split the expression IEnumerable<City> largeCitiesQuery = from country in countries from city in country.Cities where city.Population > 10000 select city; List<City> largeCitiesList2 = largeCitiesQuery.ToList();
![]() |
---|
В документации LINQ к именам переменных, в которых хранятся запросы, добавляется слово "query" (запрос). В именах переменных, в которых хранятся фактические результаты, слово "query" (запрос) отсутствует. |
Дополнительные сведения о различных способах выражения запросов содержатся в разделе Query Syntax and Method Syntax in LINQ.
Явная и неявная типизация переменных запроса
В данной документации обычно явно указывается тип переменной запроса для того, чтобы продемонстрировать типичное отношение между переменной запроса и предложением select. Кроме того, также можно использовать ключевое слово var, для указания компилятору вывести тип переменной запроса (или любой другой локальной переменной) во время компиляции. Например, ранее приведенный в данном разделе пример запроса также может быть выражен путем неявной типизации:
// Use of var is optional here and in all queries. // queryCities is an IEnumerable<City> just as // when it is explicitly typed. var queryCities = from city in cities where city.Population > 100000 select city;
Дополнительные сведения см. в разделах Неявно типизированные локальные переменные и Type Relationships in LINQ Query Operations.
Начало выражения запроса
Выражение запроса должно начинаться с предложения from
.
Оно задает источник данных вместе с переменной диапазона. Переменная диапазона
предоставляет каждый последующий элемент в исходной последовательности во время
ее обзора. Переменная диапазона строго типизируется на основе типа элементов в
источнике данных. В следующем примере переменная диапазона типизируется как Country
,
так как countries
является
массивом объектов Country
.
Так как переменная диапазона строго типизируется, для доступа к любым доступным
элементам типа можно использовать оператор-точку.
IEnumerable<Country> countryAreaQuery = from country in countries where country.Area > 500000 //sq km select country;
Переменная диапазона находится в области до тех пор, пока запрос не завершится с помощью точки с запятой или предложенияпродолжения.
Выражение запроса может содержать несколько предложений from
.
Используйте дополнительные предложения from
,
если каждый элемент в источнике является коллекцией или содержит коллекцию.
Например, предположим, что имеется коллекция объектов Country
,
каждый их которых содержит коллекцию объектов City
с
именем Cities
.
Для выполнения запросов к объектам City
в
каждой коллекцииCountry
используйте
два предложения from
,
как показано ниже:
IEnumerable<City> cityQuery = from country in countries from city in country.Cities where city.Population > 10000 select city;
Дополнительные сведения см. в разделе Предложение from.
Окончание выражения запроса
Выражение запроса должно завершаться предложением select
или group
.
Предложение "group"
Используйте предложение group
для
получения последовательности групп, организованной на основе указанного ключа.
Ключом могут быть данные любого типа. Например, следующий запрос создает
последовательность групп, содержащую один или несколько объектовCountry
,
ключ для которых имеет значение char
.
var queryCountryGroups = from country in countries group country by country.Name[0];
Дополнительные сведения о группировании см. в разделе Предложение group.
Предложение "select"
Используйте предложение select
для
получения всех других типов последовательностей. Простое предложение select
просто
создает последовательность с тем же типом объектов, что и у объектов, которые
содержатся в источнике данных. В этом примере источник данных содержит объекты
типа Country
.
Предложение orderby
просто
сортирует элементы в новом порядке, а предложение select
создает
последовательность переупорядоченных объектов Country
.
IEnumerable<Country> sortedQuery = from country in countries orderby country.Area select country;
Предложение select
может
использоваться для преобразования исходных данных в последовательности новых
типов. Такое преобразование также называют проекцией.
В следующем примере предложение select
создает проекцию последовательности
анонимных типов, содержащую только подмножество полей оригинального элемента.
Обратите внимание, что новые объекты инициализируются с помощью инициализатора
объекта.
// Here var is required because the query // produces an anonymous type. var queryNameAndPop = from country in countries select new { Name = country.Name, Pop = country.Population };
Дополнительные сведения обо всех методах использования предложения select
для
преобразования исходных данных см. в разделеПредложение
select.
Продолжения с использованием ключевого слова "into"
Ключевое слово into
можно
использовать в предложении select
или group
для
создания временного идентификатора, в котором хранится запрос. Это действие
рекомендуется выполнять, если требуется выполнить в запросе дополнительные
операции запроса после операции группирования или выбора. В следующем примере
объекты countries
группируются
в соответствии с численностью населения в диапазоны по 10 миллионов. После
создания этих групп дополнительные предложения отфильтровывают некоторые группы,
а затем сортируют группы в порядке возрастания. Чтобы выполнить эти
дополнительные операции, требуется продолжение, представляемое с помощью countryGroup
.
// percentileQuery is an IEnumerable<IGrouping<int, Country>> var percentileQuery = from country in countries let percentile = (int) country.Population / 10000000 group country by percentile into countryGroup where countryGroup.Key >= 20 orderby countryGroup.Key select countryGroup; // grouping is an IGrouping<int, Country> foreach (var grouping in percentileQuery) { Console.WriteLine(grouping.Key); foreach (var country in grouping) Console.WriteLine(country.Name + ":" + country.Population); }
Дополнительные сведения см. в разделе into.
Фильтрация, упорядочение и присоединение
Между открывающим предложением from
и
завершающим предложением select
или group
могут
размещаться все остальные, необязательные предложения (where
, join
, orderby
, from
, let
).
Любое необязательное предложение может использоваться в теле запроса несколько
раз или отсутствовать вообще.
Предложение "where"
Используйте предложение where
для
фильтрации элементов из источника данных по одному или нескольким выражениям
предиката. У предложения where
в
следующем примере имеются два предиката.
IEnumerable<City> queryCityPop = from city in cities where city.Population < 200000 && city.Population > 100000 select city;
Дополнительные сведения см. в разделе Предложение where.
Предложение "orderby"
Используйте предложение orderby
,
чтобы сортировать результаты в порядке возрастания или убывания. Также можно
задать порядок дополнительной сортировки. В следующем примере выполняется
основная сортировка объектов country
по
свойству Area
.
Затем выполняется дополнительная сортировка по свойству Population
.
IEnumerable<Country> querySortedCountries = from country in countries orderby country.Area, country.Population descending select country;
Ключевое слово ascending
является
необязательным, так как сортировка по умолчанию происходит по возрастанию, если
не задан порядок сортировки. Дополнительные сведения см. в разделе Предложение
orderby.
Предложение "join"
Используйте предложение join
для
связи или объединения элементов из одного источника данных с элементами из
другого источника данных на основе сравнения на равенство определенных ключей в
каждом элементе. В LINQ операции объединения выполняются над
последовательностями объектов, элементы которых относятся к различным типам.
После объединения двух последовательностей необходимо использовать оператор select
или group
,
чтобы указать элемент для сохранения в выходной последовательности. Также можно
использовать анонимный тип, чтобы объединить свойства каждого набора связанных
элементов в новый тип для выходной последовательности. В следующем примере
связываются объекты prod
,
свойство Category
которых
соответствует одной из категорий в массиве строк categories
.
Отфильтровываются продукты, свойство Category
которых
не соответствует ни одной строке в categories
.
Оператор select
формирует
новый тип, свойства которого берутся как из cat
,
так и из prod
.
var categoryQuery = from cat in categories join prod in products on cat equals prod.Category select new { Category = cat, Name = prod.Name };
Также можно выполнить групповое соединение путем сохранения результатов операции join
во
временную переменную, используя ключевое слово into.
Дополнительные сведения см. в разделе Предложение
join.
Предложение "let"
Используйте предложение let
для
сохранения результатов выражения, такого как вызов метода, в новую переменную
диапазона. В следующем примере в переменную диапазона firstName
сохраняется
первый элемент массива строк, возвращенного с помощью Split
.
string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" }; IEnumerable<string> queryFirstNames = from name in names let firstName = name.Split(new char[] { ' ' })[0] select firstName; foreach (string s in queryFirstNames) Console.Write(s + " "); //Output: Svetlana Claire Sven Cesar
Дополнительные сведения см. в разделе Предложение let.
Вложенные запросы в выражении запроса
Предложение запроса может само содержать выражение запроса, которое иногда
называют вложенным
запросом. Каждый вложенный запрос начинается собственным предложением from
,
которое может указывать на источник данных, отличный от источника данных первого
предложения from
.
Например, в следующем запросе показано выражение запроса, которое используется в
операторе "select" для извлечения результатов операции группирования.
var queryGroupMax = from student in students group student by student.GradeLevel into studentGroup select new { Level = studentGroup.Key, HighestScore = (from student2 in studentGroup select student2.Scores.Average()) .Max() };
Лучше всего для создания запросов использовать синтаксис
запроса, создавая выражения
запросов. В следующем примере показано три выражения запроса. В первом
выражении демонстрируется фильтрация или ограничение результатов путем
применения условий в предложении where
.
Оно возвращает все элементы в исходной последовательности со значениями больше 7
и меньше 3. Второе выражение демонстрирует сортировку возвращаемых результатов.
Третий запрос демонстрирует группировку результатов. Он возвращает две группы на
основе первой буквы слова.
// Query #1. List<int> numbers = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; // The query variable can also be implicitly typed by using var IEnumerable<int> filteringQuery = from num in numbers where num < 3 || num > 7 select num; // Query #2. IEnumerable<int> orderingQuery = from num in numbers where num < 3 || num > 7 orderby num ascending select num; // Query #3. string[] groupingQuery = { "carrots", "cabbage", "broccoli", "beans", "barley" }; IEnumerable<IGrouping<char, string>> queryFoodGroups = from item in groupingQuery group item by item[0];
Обратите внимание, что тип запросов — IEnumerable<T>.
Все эти запросы можно написать с помощью var
,
как показано в примере ниже.
var query = from num in numbers...
В каждом из приведенных выше примеров фактическое выполнение запроса
откладывается до использования переменной запроса в операторе foreach
.
Дополнительные сведения см. в разделе Introduction
to LINQ Queries (C#).
Некоторые операции запросов должны быть выражены в виде вызова метода. Чаще всего используются методы, возвращающие одноэлементные числовые значения, например Sum, Max, Min, Average и т.д. Эти методы всегда должны быть вызваны последними в запросе, поскольку они представляют только одно значение и не могут служить источником дополнительных действий запроса. В следующем примере демонстрируется вызов метода в выражении запроса.
List<int> numbers1 = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; List<int> numbers2 = new List<int>() { 15, 14, 11, 13, 19, 18, 16, 17, 12, 10 }; // Query #4. double average = numbers1.Average(); // Query #5. IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);
Если у метода есть параметры, они представлены в виде лямбда-выражения, как показано в следующем примере.
// Query #6. IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);
В предыдущих запросах только запрос №4 выполнялся немедленно. Причина
заключается в том, что он возвращает одиночное значение, а не универсальную
коллекцию IEnumerable<T>.
Сам метод должен использовать foreach
для
вычисления значения.
Любой их перечисленных ранее запросов можно написать с использованием неявной типизации с помощью var, как показано в следующем примере.
// var is used for convenience in these queries var average = numbers1.Average(); var concatenationQuery = numbers1.Concat(numbers2); var largeNumbersQuery = numbers2.Where(c => c > 15);
В этом примере демонстрируется использование синтаксиса метода для результатов предложения запроса. Нужно всего лишь заключить выражение запроса в скобки, а затем применить оператор точки и вызвать метод. В следующем примере запрос #7 возвращает количество чисел, значение которых лежит в диапазоне от 3 до 7. Однако в общем случае лучше использовать вторую переменную для хранения результатов вызова метода. Таким образом, будет меньше вероятность перепутать запрос с результатами запроса.
// Query #7. // Using a query expression with method syntax int numCount1 = (from num in numbers1 where num < 3 || num > 7 select num).Count(); // Better: Create a new variable to store // the method call result IEnumerable<int> numbersQuery = from num in numbers1 where num < 3 || num > 7 select num; int numCount2 = numbersQuery.Count();
Запрос №7 возвращает одиночное значение, а не коллекцию, поэтому он выполняется мгновенно.
Предыдущий запрос можно написать с использованием неявной типизации с помощью var
,
как показано в следующем примере:
var numCount = (from num in numbers...
Можно использовать синтаксис метода следующим образом.
var numCount = numbers.Where(n => n < 3 || n > 7).Count();
Можно использовать явную типизацию следующим образом.
int numCount = numbers.Where(n => n < 3 || n > 7).Count();
В следующем примере показано, как перенести код обработки исключений вне выражения запроса. Это возможно только в том случае, когда метод не зависит от переменных, являющихся локальными для запроса.
class ExceptionsOutsideQuery { static void Main() { // DO THIS with a datasource that might // throw an exception. It is easier to deal with // outside of the query expression. IEnumerable<int> dataSource; try { dataSource = GetData(); } catch (InvalidOperationException) { // Handle (or don't handle) the exception // in the way that is appropriate for your application. Console.WriteLine("Invalid operation"); goto Exit; } // If we get here, it is safe to proceed. var query = from i in dataSource select i * i; foreach (var i in query) Console.WriteLine(i.ToString()); //Keep the console window open in debug mode Exit: Console.WriteLine("Press any key to exit"); Console.ReadKey(); } // A data source that is very likely to throw an exception! static IEnumerable<int> GetData() { throw new InvalidOperationException(); } }
В некоторых случаях наилучшим решением при возникновении исключения в запросе
является немедленная остановка выполнения запроса. В следующем примере показано,
как обрабатывать исключения, которые могут возникнуть внутри тела запроса.
Предположим, чтоSomeMethodThatMightThrow
может
вызвать исключение, для которого потребуется остановить выполнение запроса.
Обратите внимание, что блок try
заключает
в себя цикл foreach
,
а не сам запрос. Причина заключается в том, что цикл foreach
—
это точка, в которой запрос фактически выполняется. Дополнительные сведения см.
в разделе Introduction
to LINQ Queries (C#).
class QueryThatThrows { static void Main() { // Data source. string[] files = { "fileA.txt", "fileB.txt", "fileC.txt" }; // Demonstration query that throws. var exceptionDemoQuery = from file in files let n = SomeMethodThatMightThrow(file) select n; // Runtime exceptions are thrown when query is executed. // Therefore they must be handled in the foreach loop. try { foreach (var item in exceptionDemoQuery) { Console.WriteLine("Processing {0}", item); } } // Catch whatever exception you expect to raise // and/or do any necessary cleanup in a finally block catch (InvalidOperationException e) { Console.WriteLine(e.Message); } //Keep the console window open in debug mode Console.WriteLine("Press any key to exit"); Console.ReadKey(); } // Not very useful as a general purpose method. static string SomeMethodThatMightThrow(string s) { if (s[4] == 'C') throw new InvalidOperationException(); return @"C:\newFolder\" + s; } } /* Output: Processing C:\newFolder\fileA.txt Processing C:\newFolder\fileB.txt Operation is not valid due to the current state of the object. */
В следующем примере показано использование именованного типа Student
для
хранения объединенных данных из двух коллекций строк в памяти, которые имитируют
данные электронной таблицы в формате CSV. Первый набор строк представляет имена
и идентификаторы студентов, а вторая коллекция представляет идентификатор
студента (в первом столбце) и четыре результата экзаменов. Идентификатор
используется в качестве внешнего ключа.
class Student { public string FirstName { get; set; } public string LastName { get; set; } public int ID { get; set; } public List<int> ExamScores { get; set; } } class PopulateCollection { static void Main() { // These data files are defined in How to: Join Content from // Dissimilar Files (LINQ). // Each line of names.csv consists of a last name, a first name, and an // ID number, separated by commas. For example, Omelchenko,Svetlana,111 string[] names = System.IO.File.ReadAllLines(@"../../../names.csv"); // Each line of scores.csv consists of an ID number and four test // scores, separated by commas. For example, 111, 97, 92, 81, 60 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 ExamScores member. We skip // the first item in the split string because it is the student ID, // not an exam score. IEnumerable<Student> queryNamesScores = from nameLine in names let splitName = nameLine.Split(',') from scoreLine in scores let splitScoreLine = scoreLine.Split(',') where splitName[2] == splitScoreLine[0] select new Student() { FirstName = splitName[0], LastName = splitName[1], ID = Convert.ToInt32(splitName[2]), ExamScores = (from scoreAsText in splitScoreLine.Skip(1) select Convert.ToInt32(scoreAsText)). ToList() }; // Optional. Store the newly created student objects in memory // for faster access in future queries. This could be useful with // very large data files. List<Student> students = queryNamesScores.ToList(); // Display each student's name and exam score average. 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 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. */
В предложении select инициализатор
объектов используется для создания каждого нового объекта Student
,
применяя данные из двух источников.
Если нет необходимости хранить результаты запроса, анонимные типы могут быть более удобными, чем именованные типы. Именованные типы необходимы, если результаты запроса передаются за пределы метода, в котором выполняется запрос. В следующем примере выполняется та же задача, что и в предыдущем примере, но с использованием анонимных типов вместо именованных:
// Merge the data sources by using an anonymous type. // Note the dynamic creation of a list of ints for the // ExamScores member. We skip 1 because the first string // in the array is the student ID, not an exam score. var queryNamesScores2 = from nameLine in names let splitName = nameLine.Split(',') from scoreLine in scores let splitScoreLine = scoreLine.Split(',') where splitName[2] == splitScoreLine[0] select new { First = splitName[0], Last = splitName[1], ExamScores = (from scoreAsText in splitScoreLine.Skip(1) select Convert.ToInt32(scoreAsText)) .ToList() }; // Display each student's name and exam score average. foreach (var student in queryNamesScores2) { Console.WriteLine("The average score of {0} {1} is {2}.", student.First, student.Last, student.ExamScores.Average()); }
Группирование — одна из самых эффективных функций LINQ. В следующих примерах демонстрируются различные способы группирования данных:
-
По отдельному свойству.
-
По первой букве строкового свойства.
-
По расчетному числовому диапазону.
-
По логическому предикату или другому выражению.
-
По составному ключу.
Кроме того, два последних запроса передают свои результаты в новый анонимный тип, содержащий только имя и фамилию студента. Дополнительные сведения см. в разделе Предложение group.
Во всех примерах в данном разделе используются следующие вспомогательные классы и источники данных.
public class StudentClass { #region data protected enum GradeLevel { FirstYear = 1, SecondYear, ThirdYear, FourthYear }; protected class Student { public string FirstName { get; set; } public string LastName { get; set; } public int ID { get; set; } public GradeLevel Year; public List<int> ExamScores; } protected static List<Student> students = new List<Student> { new Student {FirstName = "Terry", LastName = "Adams", ID = 120, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 99, 82, 81, 79}}, new Student {FirstName = "Fadi", LastName = "Fakhouri", ID = 116, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 99, 86, 90, 94}}, new Student {FirstName = "Hanying", LastName = "Feng", ID = 117, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 93, 92, 80, 87}}, new Student {FirstName = "Cesar", LastName = "Garcia", ID = 114, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 97, 89, 85, 82}}, new Student {FirstName = "Debra", LastName = "Garcia", ID = 115, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 35, 72, 91, 70}}, new Student {FirstName = "Hugo", LastName = "Garcia", ID = 118, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 92, 90, 83, 78}}, new Student {FirstName = "Sven", LastName = "Mortensen", ID = 113, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 88, 94, 65, 91}}, new Student {FirstName = "Claire", LastName = "O'Donnell", ID = 112, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 75, 84, 91, 39}}, new Student {FirstName = "Svetlana", LastName = "Omelchenko", ID = 111, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 97, 92, 81, 60}}, new Student {FirstName = "Lance", LastName = "Tucker", ID = 119, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 68, 79, 88, 92}}, new Student {FirstName = "Michael", LastName = "Tucker", ID = 122, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 94, 92, 91, 91}}, new Student {FirstName = "Eugene", LastName = "Zabokritski", ID = 121, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 96, 85, 91, 60}} }; #endregion //Helper method, used in GroupByRange. protected static int GetPercentile(Student s) { double avg = s.ExamScores.Average(); return avg > 0 ? (int)avg / 10 : 0; } public void QueryHighScores(int exam, int score) { var highScores = from student in students where student.ExamScores[exam] > score select new {Name = student.FirstName, Score = student.ExamScores[exam]}; foreach (var item in highScores) { Console.WriteLine("{0,-15}{1}", item.Name, item.Score); } } } public class Program { public static void Main() { StudentClass sc = new StudentClass(); sc.QueryHighScores(1, 90); // Keep the console window open in debug mode. Console.WriteLine("Press any key to exit"); Console.ReadKey(); } }
В следующем примере показывается группировка элементов источника с помощью
использования отдельного свойства элемента в качестве ключа группы. В данном
случае в качестве ключа используется string
,
фамилия учащегося. Для ключа можно также использовать подстроку. При операции
группирования используется компаратор проверки на равенство, используемый по
умолчанию для данного типа.
Вставьте приведенный ниже метод в класс StudentClass
.
Измените оператор вызова в методе Main
на sc.GroupBySingleProperty()
.
public void GroupBySingleProperty() { Console.WriteLine("Group by a single property in an object:"); // Variable queryLastNames is an IEnumerable<IGrouping<string, // DataClass.Student>>. var queryLastNames = from student in students group student by student.LastName into newGroup orderby newGroup.Key select newGroup; foreach (var nameGroup in queryLastNames) { Console.WriteLine("Key: {0}", nameGroup.Key); foreach (var student in nameGroup) { Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName); } } } /* Output: Group by a single property in an object: Key: Adams Adams, Terry Key: Fakhouri Fakhouri, Fadi Key: Feng Feng, Hanying Key: Garcia Garcia, Cesar Garcia, Debra Garcia, Hugo Key: Mortensen Mortensen, Sven Key: O'Donnell O'Donnell, Claire Key: Omelchenko Omelchenko, Svetlana Key: Tucker Tucker, Lance Tucker, Michael Key: Zabokritski Zabokritski, Eugene */
В следующем примере показывается группировка элементов источника, когда в качестве ключа группы используется не свойство объекта. В данном случае в качестве ключа используется первая буква фамилии учащегося.
Вставьте приведенный ниже метод в класс StudentClass
.
Измените оператор вызова в методе Main
на sc.GroupBySubstring()
.
public void GroupBySubstring() { Console.WriteLine("\r\nGroup by something other than a property of the object:"); var queryFirstLetters = from student in students group student by student.LastName[0]; foreach (var studentGroup in queryFirstLetters) { Console.WriteLine("Key: {0}", studentGroup.Key); // Nested foreach is required to access group items. foreach (var student in studentGroup) { Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName); } } } /* Output: Group by something other than a property of the object: Key: A Adams, Terry Key: F Fakhouri, Fadi Feng, Hanying Key: G Garcia, Cesar Garcia, Debra Garcia, Hugo Key: M Mortensen, Sven Key: O O'Donnell, Claire Omelchenko, Svetlana Key: T Tucker, Lance Tucker, Michael Key: Z Zabokritski, Eugene */
В следующем примере показано, как создавать вложенные группы в выражении запроса LINQ. Каждая группа, созданная в соответствии с годом или уровнем обучения, затем подразделяется на группы на основе имен учащихся.
public void QueryNestedGroups() { var queryNestedGroups = from student in students group student by student.Year into newGroup1 from newGroup2 in (from student in newGroup1 group student by student.LastName) group newGroup2 by newGroup1.Key; // Three nested foreach loops are required to iterate // over all elements of a grouped group. Hover the mouse // cursor over the iteration variables to see their actual type. foreach (var outerGroup in queryNestedGroups) { Console.WriteLine("DataClass.Student Level = {0}", outerGroup.Key); foreach (var innerGroup in outerGroup) { Console.WriteLine("\tNames that begin with: {0}", innerGroup.Key); foreach (var innerGroupElement in innerGroup) { Console.WriteLine("\t\t{0} {1}", innerGroupElement.LastName, innerGroupElement.FirstName); } } } } /* Output: DataClass.Student Level = SecondYear Names that begin with: Adams Adams Terry Names that begin with: Garcia Garcia Hugo Names that begin with: Omelchenko Omelchenko Svetlana DataClass.Student Level = ThirdYear Names that begin with: Fakhouri Fakhouri Fadi Names that begin with: Garcia Garcia Debra Names that begin with: Tucker Tucker Lance DataClass.Student Level = FirstYear Names that begin with: Feng Feng Hanying Names that begin with: Mortensen Mortensen Sven Names that begin with: Tucker Tucker Michael DataClass.Student Level = FourthYear Names that begin with: Garcia Garcia Cesar Names that begin with: O'Donnell O'Donnell Claire Names that begin with: Zabokritski Zabokritski Eugene */