17325

Язык LINQ

Лекция

Информатика, кибернетика и программирование

Лекция 11. Язык LINQ План 1. Основы языка LINQ 2. LINQ: обобщения и интерфейсы 3. Основные операции запроса 4. Преобразования данных с LINQ 5. Связи типов в операциях запроса 6. Синтаксис запроса или синтаксис метода 1. Основы языка LINQ Language Integrated Query LINQ – проект Microsoft по ...

Украинкский

2013-06-30

145.16 KB

26 чел.

Лекция 11. Язык LINQ

План

1. Основы языка LINQ

2. LINQ: обобщения и интерфейсы

3. Основные операции запроса

4. Преобразования данных с LINQ

5. Связи типов в операциях запроса

6. Синтаксис запроса или синтаксис метода

1. Основы языка LINQ

Language Integrated Query (LINQ) – проект Microsoft по добавлению синтаксиса языка запросов, напоминающего SQL, в языки программирования платформы .NET Framework. LINQ выпущен вместе с Visual Studio 2008 в конце ноября 2007 года.

LINQ (Language-Integrated Query) представляет собой набор функций Visual Studio 2008, расширяющих мощные возможности запроса в синтаксисе языка C# и Visual Basic. LINQ представляет стандартные шаблоны для создания запросов и обновления данных; технология может быть расширена для поддержки потенциально любого типа хранилища данных. Visual Studio 2008 включает сборки поставщиков LINQ, позволяющие использовать LINQ с коллекциями платформы .NET Framework, базами данных SQL Server, наборами данных ADO.NET и XML-документами.

LINQ представляет собой набор расширений языка, поддерживающий формирование запросов данных способом, безопасным по типам. Запрашиваемые данные могут быть представлены в форме XML (запросы LINQ к XML), баз данных (ADO.NET с поддержкой LINQ, куда входят LINQ к SQL, LINQ к наборам данных и LINQ к экземплярам), объектов (LINQ к объектам) и т.д. Архитектура LINQ показана на рис. 1.

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

Рис. 1.  Архитектура LINQ

Рассмотрим пример интегрированного запроса:

var contacts =

from c in customers

where c.City == "Москва"

select new { c.Name, c.Phone };

Здесь из некоторого источника данных customers выбираются имена и телефоны клиентов, проживающих в городе Москва. Запрос очень похож на язык SQL, однако использует строго типизированный синтаксис.

Этот же запрос можно записать и в другом виде – используя лямбда-выражения и методы расширения (рис. 2). Именно к такому виду приведет код, приведенный в примере выше, компилятор.


Рис. 2.  Два способа написания запроса LINQ на языке C# 3.0

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

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

  1.  получение источника данных;
  2.  создание запроса;
  3.  выполнение запроса.

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

class IntroToLINQ

{        

   static void Main()

   {

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

       int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

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

       // numQuery – это IEnumerable<int>

       var numQuery =

           from num in numbers

           where (num % 2) == 0

           select num;

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

       foreach (int num in numQuery)

       {

           Console.Write("{0,1} ", num);

       }

   }

}

Результатом выполнения запроса будет вывод на консоль только четных чисел.

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


Рис. 3.  Операция запроса в LINQ

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

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

Интерфейс это полностью абстрактный класс, все методы которого абстрактные (не содержат реализации).

Если класс можно рассматривать как некоторый контейнер объектов перечислення, то для того, чтобы класс мог возвращать эти объекты в цикле for each, класс должен быть наследником интерфейса IEnumerable или иметь в своем составе итераторы - методы, которые возвращают результат типа IEnumerable.

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

// Создание источника данных из XML-документа, используя System.Xml.Linq

XElement contacts = XElement.Load(@"c:\myContactList.xml");

Используя LINQ to SQL, сначала вручную либо с помощью конструктора объектов создается объектно-реляционное сопоставление в режиме разработки. Затем можно написать запросы к объектам, а во время выполнения LINQ to SQL будет осуществлять взаимодействие с базой данных. В следующем примере Customer представляет определенную таблицу в базе данных, а Table<Customer> поддерживает универсальный интерфейс IQueryable<(Of <(T>)>), производный от IEnumerable<(Of <(T>)>):

// Создание источника данных из базы данных SQL Server, используя System.Data.Linq

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

1.2.  Запрос

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

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

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

Выполнение запроса разделяют на отложенное и принудительное (немедленное).

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

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

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<(Of <(TSource>)>) или ToArray<(Of <(TSource>)>):

List<int> numQuery2 =

(from num in numbers

where (num % 2) == 0

select num).ToList();

// или так:

// numQuery3 – это по прежнему int[]

var numQuery3 =

(from num in numbers

where (num % 2) == 0

select num.ToArray();

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

2. LINQ: обобщения и интерфесы

Запросы LINQ основаны на обобщениях (generic), которые впервые были представлены в .NET Framework версии 2.0.

Обобщения (generic, универсальные типы) были добавлены в язык C# версии 2.0 и среду CLR. Обобщения в платформе .NET Framework представляют концепцию параметров типов, которые позволяют разрабатывать классы и методы, не придерживающиеся спецификации одного или нескольких типов до тех пор, пока класс или метод не будет объявлен клиентским кодом и пока не будет создан его экземпляр.

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

С обобщением связано понятие интерфейса, в частности стандартного интерфейса IEnumerable<(Of <(T>)>)

Для написания запросов не требуется глубокое знание обобщений и интерфейсов. Но понимание двух основных понятий может пригодиться.

  1.  При создании экземпляра класса универсальной коллекции, например List<(Of <(<T>)>), "T" заменяется типом объектов, которые будут храниться в списке. Например, список строк выражается как List<string>, а список объектов Customer выражается как List<Customer>. Универсальный список является строго типизированным и предоставляет множество преимуществ над коллекциями, которые хранят свои элементы как Object. При попытке добавить Customer к List<string> возникнет ошибка во время компиляции. Использование универсальных коллекций не вызывает сложностей, поскольку не нужно выполнять приведение типов во время выполнения.
  2.  IEnumerable<(Of <(T>)>) является интерфейсом, который позволяет классам универсальных коллекций поддерживать перечисление с помощью оператора foreach. Классы универсальных коллекций поддерживают IEnumerable<(Of <(T>)>) так же, как не универсальные классы коллекций, например ArrayList, поддерживают IEnumerable.

2.1. Переменные IEnumerable в запросах LINQ

Переменные запросов LINQ определены как IEnumerable<(Of <(T>)>) или как производный тип, например IQueryable<(Of <(T>)>). Если переменная запроса имеет тип IEnumerable<Customer>, это означает, что запрос при выполнении выведет последовательность из нуля или более объектов Customer:

IEnumerable<Customer> customerQuery =

   from cust in customers

   where cust.City == "Москва"

   select cust;

При желании обычного синтаксиса универсальных шаблонов можно избежать с помощью ключевого слова var. Ключевое слово var сообщает компилятору о необходимости определения типа переменной запроса с помощью просмотра источника данных, указанного в предложении from. В следующем примере создается тот же самый скомпилированный код, что и в предыдущем примере:

var customerQuery2 =

   from cust in customers

   where cust.City == "Москва"

   select cust;

foreach(var customer in customerQuery2)

{

   Console.WriteLine(customer.LastName + ", " + customer.FirstName);

}

Ключевое слово var удобно, когда тип переменной является очевидным, или когда не требуется явно указывать вложенные универсальные типы, например создаваемые запросами group. В целом, если используется var, важно осознавать, что код может быть более сложным для чтения.

Пример.

Определим в качестве источника данных класс:

class Customer

   {

       public string First { get; set; }

       public string Last { get; set; }

       public int ID { get; set; }

       public string City { get; set; }

       public string Zakaz;

   }

Выбираем заказчиков из Киева

//customers

          List<Customer> customers = new List<Customer>()

       {

           new Customer {First="Александр",

               Last="Андрусенко",

               ID=100,

               City="Киев",

               Zakaz = "конфеты"},

            new Customer {First="Иван",

               Last="Гринев",

               ID=101,

               City="Киев",

               Zakaz = "конфеты"}

       };

         var customerQuery2 =

             from cust in customers

             where cust.City == "Киев"

             select cust;

           foreach (var customer in customerQuery2)

           {

               Console.WriteLine(customer.Last + ", " + customer.First);

           }

              Console.ReadKey();

3. Основные операции запроса

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

В первую очередь в запросе LINQ нужно указать источник данных. В C#, как и в большинстве языков программирования, переменная должна быть объявлена до ее использования. В запросе LINQ первым идет предложение from для указания источника данных (customers) и переменная диапазона (cust):

//queryAllCustomers – это IEnumerable<Customer>

var queryAllCustomers = from cust in customers

   select cust;

Переменная диапазона схожа с переменной итерации в цикле foreach за исключением того, что в выражении запроса не происходит фактической итерации. При выполнении запроса переменная диапазона будет использоваться как ссылка на каждый последующий элемент в customers. Поскольку компилятор может определить тип cust, нет необходимости указывать его в явном виде. Дополнительные переменные диапазона могут быть введены предложением let.

3.2. Фильтрация

Возможно, наиболее распространенной операцией запроса является применение фильтра в виде логического выражения. Фильтр приводит к возвращению запросом только тех элементов, для которых выражение является истинным. Результат создается с помощью предложения where. Фильтр фактически указывает элементы для исключения из исходной последовательности. В следующем примере возвращаются только customers, находящиеся в Москве:

var queryMoscowCustomers = from cust in customers

                          where cust.City == "Москва"

                      select cust;

Для применения нужного числа выражений фильтра в предложении where можно использовать знакомые логические операторы C# AND и OR. Например, для получения только заказчиков из Москвы и с именем Иван следует написать следующий код:

where cust.City=="Москва" && cust.Name == "Иван"

Для получения заказчиков из Москвы или Смоленска следует написать следующий код:

where cust.City == "Москва" || cust.City == "Смоленск"

3.3. Упорядочение

Часто целесообразно отсортировать возвращенные данные. Предложение orderby сортирует элементы возвращаемой последовательности в зависимости от компаратора по умолчанию для сортируемого типа. Например, следующий запрос может быть расширен для сортировки результатов на основе свойства Name. Поскольку Name является строкой, сравнение по умолчанию выполняется в алфавитном порядке от А до Я:

var queryMoscowCustomers3 =

from cust in customers

where cust.City == "Москва"

 orderby cust.Name ascending

     select cust;

Для упорядочения результатов в обратном порядке от Я до А используется предложение orderbydescending.

3.4. Группировка

Предложение group позволяет группировать результаты на основе указанного ключа. Например, можно указать, что результаты должны быть сгруппированы по City так, чтобы все заказчики из Москвы или Смоленска оказались в отдельных группах. В этом случае ключом является cust.City.

Примечание. Для демонстрации данного принципа в следующих примерах используются явные типы. Также можно использовать неявную типизацию для custQuery, group и customer, позволяя компилятору определить точный тип:

// queryCustomersByCity – это IEnumerable<IGrouping<string, Customer>>

var queryCustomersByCity =

from cust in customers

group cust by cust.City;

// customerGroup – это IGrouping<string, Customer>

foreach (var customerGroup in queryCustomersByCity)

{

     Console.WriteLine(customerGroup.Key);

     foreach (Customer customer in customerGroup)

     {

         Console.WriteLine("    {0}", customer.Name);

     }

}

Когда запрос завершается предложением group, результаты представляются в виде списка из списков. Каждый элемент в списке является объектом, имеющим член Key и список элементов, сгруппированных по этому ключу. При итерации запроса, создающего последовательность групп, необходимо использовать вложенный цикл foreach. Внешний цикл выполняет итерацию каждой группы, а внутренний цикл – итерацию членов каждой группы.

Если необходимо ссылаться на результаты операции группировки, можно использовать ключевое слово into для создания идентификатора, который можно будет запрашивать. Следующий запрос возвращает только те группы, которые содержат более двух заказчиков:

// custQuery – это IEnumerable<IGrouping<string, Customer>>

var custQuery =

from cust in customers

group cust by cust.City into custGroup

where custGroup.Count() > 2

 orderby custGroup.Key

     select custGroup;

3.5. Соединение

Операции соединения создают связи между последовательностями, неявно смоделированными в источниках данных. Например, можно выполнить соединение для поиска всех заказчиков в Москве, заказавших продукты у поставщиков в Париже. В LINQ предложение join всегда работает с коллекциями объектов, а не непосредственно с таблицами базы данных. В LINQ нет необходимости использовать join так часто, как в SQL, так как внешние ключи в LINQ представлены в объектной модели свойствами, содержащими коллекцию элементов. Например, объект Customer содержит коллекцию объектов Order. Вместо выполнения соединения, доступ к заказам можно получить с помощью точечной нотации:

from order in Customer.Orders...

3.6.  Выбор (Проецирование)

Предложение select создает результаты запроса и задает форму или тип каждого возвращаемого элемента. Например, можно указать, будут ли результаты состоять из полных объектов Customer, только из одного члена, подмножества членов или некоторых совершенно других типов, на основе вычислений или создания новых объектов. Когда предложение select создает что-либо отличное от копии исходного элемента, операция называется проекцией. Использование проекций для преобразования данных является мощной возможностью выражений запросов LINQ.

4. Преобразования данных с LINQ

LINQ используется не только для извлечения данных. Это также мощное средство для преобразования данных. С помощью запроса LINQ можно использовать исходную последовательность в качестве входных данных и изменять ее различными способами для создания новой выходной последовательности. Можно изменить саму последовательность, не изменяя элементов, при помощи сортировки и группировки. Но, возможно, наиболее мощной функцией запросов LINQ является возможность создания новых типов. Это выполняется в предложении select. Например, можно выполнить следующие задачи:

  1.  Объединить несколько входных последовательностей в одну выходную последовательность, которая имеет новый тип.
  2.  Создать выходные последовательности, элементы которых состоят только из одного или нескольких свойств каждого элемента в исходной последовательности.
  3.  Создать выходные последовательности, элементы которых состоят из результатов операций, выполняемых над исходными данными.
  4.  Создать выходные последовательности в другом формате. Например, можно преобразовать данные из строк SQL или текстовых файлов в XML.

Это только несколько примеров. Разумеется, эти преобразования могут объединяться различными способами в одном запросе. Более того, выходные последовательности одного запроса могут использоваться как входные последовательности для нового запроса.

4.1. Соединение нескольких входных последовательностей в одну выходную

Запрос LINQ можно использовать для создания выходной последовательности, содержащей элементы из нескольких входных последовательностей. В следующем примере показано объединение двух находящихся в памяти структур данных, но те же принципы могут применяться для соединения данных из источников XML, SQL или DataSet. Предположим, что существуют два следующих типа классов:

class Student

{

   public string First { get; set; }

   public string Last {get; set;}

   public int ID { get; set; }

   public string City { get; set; }

   public List<int> Scores;

}

class Teacher

{

   public string First { get; set; }

   public string Last { get; set; }

   public int ID { get; set; }

   public string City { get; set; }

}

В следующем примере показан запрос:

class DataTransformations

{

   static void Main()

   {

       // Создание первого источника данных

       List<Student> students = new List<Student>()

       {

           new Student {First="Светлана",

               Last="Омельченко",

               ID=111,

               City="Москва",

               Scores= new List<int> {5, 4, 5, 3}},

           new Student {First="Кристина",

               Last="Лаврова",

               ID=112,

               City="Тюмень",

               Scores= new List<int> {5, 3, 3, 4}},

           new Student {First="Иван",

               Last="Моргунов",

               ID=113,

               City="Новосибирск",

               Scores= new List<int> {5, 5, 5, 4}},

       };

       // Создание второго источника данных

       List<Teacher> teachers = new List<Teacher>()

       {                

           new Teacher {First="Анна", Last="Виннер", ID=945, City = "Москва"},

           new Teacher {First="Алексей", Last="Иващенко", ID=956, City = "Санкт-Петербург"},

           new Teacher {First="Михаил", Last="Антонов", ID=972, City = "Смоленск"}

       };

       // Создание запроса

       var peopleInMoscow = (from student in students

                   where student.City == "Москва"

                   select student.Last)

                  Concat(from teacher in teachers

                           where teacher.City == "Москва"

                           select teacher.Last);

       Console.WriteLine("Следующие студенты и учителя живут в Москве:");

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

       foreach (var person in peopleInMoscow)

       {

           Console.WriteLine(person);

       }

       Console.WriteLine("Нажмите любую кнопку для выхода!");

       Console.ReadKey();

   }

}

/* На выходе будет получено:

   Следующие студенты и учителя живут в Москве:

   Омельченко

   Виннер

*/

Полный текст программы

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace Link1

{

   class Student

   {

       public string First { get; set; }

       public string Last { get; set; }

       public int ID { get; set; }

       public string City { get; set; }

       public List<int> Scores;

   }

   class Teacher

   {

       public string First { get; set; }

       public string Last { get; set; }

       public int ID { get; set; }

       public string City { get; set; }

   }

   class Program

   {

       static void Main(string[] args)

       {

           // Создание первого источника данных

           List<Student> students = new List<Student>()

       {

           new Student {First="Светлана",

               Last="Омельченко",

               ID=111,

               City="Москва",

               Scores= new List<int> {5, 4, 5, 3}},

           new Student {First="Кристина",

               Last="Лаврова",

               ID=112,

               City="Тюмень",

               Scores= new List<int> {5, 3, 3, 4}},

           new Student {First="Иван",

               Last="Моргунов",

               ID=113,

               City="Новосибирск",

               Scores= new List<int> {5, 5, 5, 4}},

       };

           // Создание второго источника данных

           List<Teacher> teachers = new List<Teacher>()

       {                

           new Teacher {First="Анна", Last="Виннер", ID=945, City = "Москва"},

           new Teacher {First="Алексей", Last="Иващенко", ID=956, City = "Санкт-Петербург"},

           new Teacher {First="Михаил", Last="Антонов", ID=972, City = "Смоленск"}

       };

           // Создание запроса

           var peopleInMoscow = (from student in students

                                 where student.City == "Москва"

                                 select student.Last)

                       .Concat(from teacher in teachers

                               where teacher.City == "Москва"

                               select teacher.Last);

           Console.WriteLine("Следующие студенты и учителя живут в Москве:");

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

           foreach (var person in peopleInMoscow)

           {

               Console.WriteLine(person);

           }

           Console.WriteLine("Нажмите любую кнопку для выхода!");

           Console.ReadKey();

       }

   }

}

4.2. Выбор подмножества каждого исходного элемента

Существует два основных способа выбора подмножества каждого элемента в исходной последовательности.

  1.  Чтобы выбрать только один член исходного элемента, используйте обращение к свойствам. В следующем примере предполагается, что объект Customer содержит несколько открытых свойств, включая строку с именем City. При выполнении этот запрос создаст выходную последовательность строк:
  2.  var query = from cust in Customers
  3.              select cust.City;
  4.  Для создания элементов, содержащих более одного свойства исходного элемента, можно использовать инициализатор объектов либо с именованным объектом, либо с анонимным типом. В следующем примере показано использование анонимного типа для инкапсуляции двух свойств из каждого элемента Customer:
  5.  var query = from cust in Customer
  6.              select new {Name = cust.Name, City = cust.City};

4.3. Преобразование находящихся в памяти объектов в XML

Запросы LINQ упрощают преобразования данных между структурами данных в памяти, базами данных SQL, наборами данных ADO.NET и потоками или документами XML. Для работы с XML нужно подключить пространство имен.

using System.Xml.Linq;

В следующем примере объекты в находящейся в памяти в структуре данных преобразуются в XML-элементы:

class XMLTransform

{

   static void Main()

   {            

       // Создание источника данных, используя инициализацию коллекции

       List<Student> students = new List<Student>()

       {

           new Student {First="Светлана", Last="Омельченко", ID=111, City="Москва", Scores= new List<int> {5, 4, 5, 3}},

           new Student {First="Кристина", Last="Лаврова", ID=112, City="Тюмень", Scores= new List<int> {5, 3, 3, 4}},

           new Student {First="Иван", Last="Моргунов", ID=113, City="Новосибирск", Scores= new List<int> {5, 5, 5, 4}},

       };

       // Создание запроса

       var studentsToXML = new XElement("Root",

           from student in students

           let x = String.Format("{0},{1},{2},{3}", student.Scores[0],

                   student.Scores[1], student.Scores[2], student.Scores[3])

           select new XElement("student",

                      new XElement("First", student.First),

                      new XElement("Last", student.Last),

                      new XElement("Scores", x)

                   )

               );

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

       Console.WriteLine(studentsToXML);

       Console.WriteLine("Нажмите любую кнопку для выхода!");

       Console.ReadKey();

   }

}

Код формирует следующие выходные XML-данные:

<Root>

 <student>

   <First>Светлана</First>

   <Last>Омельченко</Last>

   <Scores>5,4,5,3</Scores>

 </student>

 <student>

   <First>Кристина</First>

   <Last>Лаврова</Last>

   <Scores>5,3,3,4</Scores>

 </student>

 <student>

   <First>Иван</First>

   <Last>Моргунов</Last>

   <Scores>5,5,5,4</Scores>

 </student>

</Root>

Фрагмент кода в VS

   var studentsToXML = new XElement("Root",

   from student in students

   let x = String.Format("{0},{1},{2},{3}", student.Scores[0],

           student.Scores[1], student.Scores[2], student.Scores[3])

   select new XElement("student",

              new XElement("First", student.First),

              new XElement("Last", student.Last),

              new XElement("Scores", x)

           )

       );

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

           Console.WriteLine(studentsToXML);

           Console.WriteLine("Нажмите любую кнопку для выхода!");

           Console.ReadKey();

4.4. Выполнение операций над исходными элементами

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

Примечание. Вызов методов в выражениях запроса не поддерживается, если запрос будет перенесен в какой-либо другой домен. Например, невозможно вызвать обычный C# метод в LINQ to SQL, так как в SQL Server для него отсутствует контекст. Тем не менее, хранимые процедуры можно сопоставить методам и вызывать последние:

class FormatQuery

{

   static void Main()

   {            

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

       double[] radii = { 1, 2, 3 };

       // Запрос

       IEnumerable<string> query =

           from rad in radii

           select String.Format("Area = {0}", (rad * rad) * 3.14);

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

       foreach (string s in query)

           Console.WriteLine(s);

       Console.WriteLine("Нажмите любую кнопку для выхода!");

       Console.ReadKey();

   }

}

/* На выходе будет получено:

   Area = 3.14

   Area = 12.56

   Area = 28.26

*/

5. Связи типов в операциях запроса

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

Операции запросов LINQ строго типизированы в источнике данных, в самом запросе и при выполнении запроса. Тип переменных в запросе должен быть совместим с типом элементов в источнике данных и с типом переменной итерации в операторе foreach. Строгая типизация гарантирует перехват ошибок во время компиляции, когда их можно будет исправить прежде, чем с ними столкнутся пользователи.

5.1. Запросы, не выполняющие преобразование исходных данных

На рис. 4. показана операция запроса LINQ to Objects, не выполняющая преобразование данных. Источник содержит последовательность строк, результат запроса также является последовательностью строк.


Рис. 4.  Операция запроса LINQ to Objects, не выполняющая преобразование данных

Связи типов в операциях запроса (LINQ)

  1.  Аргумент типа источника данных определяет тип переменной диапазона.
  2.  Тип выбранного объекта определяет тип переменной запроса. Здесь name является строкой. Следовательно, переменная запроса является IEnumerable<string>.
  3.  Итерация переменной запроса выполняется в операторе foreach. Поскольку переменная запроса является последовательностью строк, переменная итерации также является строкой.

5.2. Запросы, выполняющие преобразование исходных данных

На рис. 5. показана операция запроса LINQ to SQL, выполняющая простое преобразование данных. В качестве входных данных запрос получает последовательность объектов Customer и выбирает в результате только свойство Name. Поскольку Name является строкой, запрос создает последовательность строк в качестве выходных данных.


Рис. 5.  Операция запроса LINQ to Objects, выполняющая простое преобразование данных

Связи типов в операциях запроса (LINQ)

  1.  Аргумент типа источника данных определяет тип переменной диапазона.
  2.  Оператор select возвращает свойство Name вместо целого объекта Customer. Поскольку Name является строкой, аргумент типа custNameQuery является string, а не Customer.
  3.  Поскольку custNameQuery является последовательностью строк, переменная итерации цикла foreach также должна быть string.

На рис. 6 показано немного более сложное преобразование. Оператор select возвращает анонимный тип, захватывающий только два члена исходного объекта Customer.


Рис. 6.  Операция запроса LINQ to Objects, выполняющая сложное преобразование данных

Аргумент типа источника данных всегда является типом переменной диапазона в запросе.

  1.  Так как оператор select создает анонимный тип, переменная запроса должна быть неявно типизирована с помощью var.
  2.  Поскольку тип переменной запроса неявный, переменная итерации в цикле foreach также должна быть неявной.

5. 3. Разрешение компилятору определять сведения о типе

Несмотря на то, что необходимо обладать знаниями об отношениях типов в операции запроса, существует возможность передачи выполнения всех действий компилятору. Ключевое слово var можно использовать для любой локальной переменной в операции запроса (рис. 7). Компилятор в этом случае будет указывать строгий тип для каждой переменной в операции запроса.


Рис. 7.  Определение компилятором сведений о типе

6. Синтаксис запроса или синтаксис метода

В предыдущих примерах большинство запросов написаны как выражения запросов с помощью декларативного синтаксиса запроса, представленного в C# 3.0. Однако в самой общеязыковой среде выполнения (CLR) .NET отсутствует понятие синтаксиса запроса. Таким образом, во время компиляции выражения запроса преобразуются в то, что понятно CLR – вызовы методов. Эти методы называются стандартными операторами запросов, и они имеют такие имена, как Where, Select, GroupBy, Join, Max, Average и т. д. Их можно вызывать непосредственно, используя синтаксис методов вместо синтаксиса запросов.

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

6.1. Методы расширения стандартных операторов запросов

В следующем примере показано простое выражение запроса и семантически эквивалентный ему запрос, написанный как запрос на основе метода:

class QueryVMethodSyntax

{

   static void Main()

   {

       int[] numbers = { 5, 10, 8, 3, 6, 12 };

       //Синтаксис запроса:

       IEnumerable<int> numQuery1 =

           from num in numbers

           where num % 2 == 0

           orderby num

           select num;

       //Синтаксис метода:

       IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);

       foreach (int i in numQuery1)

       {

           Console.Write(i + " ");

       }

       Console.WriteLine(System.Environment.NewLine);

       foreach (int i in numQuery2)

       {

           Console.Write(i + " ");

       }

       Console.WriteLine(System.Environment.NewLine);

       Console.WriteLine("Нажмите любую кнопку для выхода!");

       Console.ReadKey();

   }

}

/*

   На выходе будет получено:

   6 8 10 12

   6 8 10 12

*/

Два примера имеют идентичные результаты. Тип переменной запроса одинаковый в обеих формах: IEnumerable<(Of <(T>)>).

Чтобы понять запрос на основе метода, рассмотрим его более детально. Обратите внимание, что в правой части выражения предложение where теперь выражено в виде метода экземпляра объекта numbers, который имеет тип IEnumerable<int>. Если вы знакомы с универсальным интерфейсом IEnumerable<(Of <(T>)> ), вам известно, что он не имеет метода Where. Однако при вызове списка завершения IntelliSense в IDE Visual Studio будет отображен не только метод Where, но и многие другие методы, такие как Select, SelectMany, Join и Orderby. Они все являются стандартными операторами запросов.

Несмотря на то, что кажется, как будто интерфейс IEnumerable<(Of <(T>)>) был переопределен для включения этих дополнительных методов, на самом деле это не так. Стандартные операторы запросов реализуются как новый тип методов, называемых методами расширения. Методы расширения "расширяют" существующий тип; их можно вызывать так, как если бы они были методами экземпляра типа. Стандартные операторы запросов расширяют IEnumerable<(Of <(T>)>), что позволяет написать numbers.Where(...).

Некоторые поставщики LINQ, например LINQ to SQL и LINQ to XML, реализуют свои собственные стандартные операторы запросов и дополнительные методы расширения для типов, отличных от IEnumerable<(Of <(T>)>).

6.2. Лямбда-выражения

В предыдущем примере условное выражение (num % 2 == 0) передается в качестве встроенного аргумента методу Where: Where(num => num % 2 == 0). Это встроенное выражение называется лямбда-выражением. Оно является удобным способом написания кода, который в противном случае пришлось бы записывать в более громоздкой форме как анонимный метод, универсальный делегат или дерево выражений. В C# => является лямбда-оператором, который читается как "переходит". num слева от оператора является входной переменной, которая соответствует num в выражении запроса. Компилятор может определить тип num, так как ему известно, что numbers является универсальным типом IEnumerable<(Of <(T>)>). Основная часть лямбда-выражения представляет то же самое, что и выражение в синтаксисе запроса или в любом другом выражении или операторе C#; она может включать вызовы методов и другую сложную логику. Возвращаемым значением является просто результат выражения.

Приступая к работе с LINQ, нет необходимости широко использовать лямбда-выражения. Однако некоторые запросы могут выражаться только в синтаксисе методов, а некоторые из них требуют лямбда-выражений. 


 

А также другие работы, которые могут Вас заинтересовать

74092. Техническая подготовка производства новых изделий 338.5 KB
  Техническая подготовка производства новых изделий Техническая подготовка производства включает в себя конструкторскую технологическую организационную подготовку производства а также освоение промышленного выпуска новых изделий. На этом этапе новое изделие проходит различные стадии его освоения от опытного образца полученного...
74093. Ғұндар және халықтардың орын ауыстыруы 33.55 KB
  Қытайдың ығыстыруымен ғұндар Алтай Сарыарқа арқылы Батыс Қазақстанға жылжыды. Тарихта бұл жылжу тек ғұндарға ғана тән құбылыс емес басқа да ірі тайпаларға да әсерін тигізді. Ғүндар Еділден өтіп алдарына сарматаландарды сала отырып Еуропаға енді.
74094. Абылай (Әбілмансұр) хан 33.02 KB
  Абылай хан 1711-1781 Қазақ Ордасының ханы қазақ мемлекетінің тарихындағы аса көрнекті мемлекет қайраткері арғы тегі Жошы хан бергі бабалары қазақ ордасының негізін салған ӘзЖәнібек одан соң еңсегей бойлы ер Есім хан Салқам Жәңгір хан. Оның баласы Әбілмансұр кейін қазаққа хан болып Абылай атанған ақтабан шұбырынды жылдарында жетім қалып үйсін Төле бидің қолына келеді. Абылай бастаған қазақ қолы жоңғар басқыншылырына бірнеше мәрте соққы берді. Сол кездегі ойрат басқыншыларына қарсы күрестің ең белсенді ұйымдастырушыларының біріне...
74095. Казак хандыгынын курылуы: ишки курылысы 29.99 KB
  XIΥXΥғасырларда Шығыс Дешті Қыпшақ Ақ Орда Әбілхайыр хандығы Жетісу Моғолстан мен Түркістан Қазақстанның отырықшыегінші ауданы аумағындағы қазақ рулары мен тайпаларының этникалық және саяси топтасуының күшейе түсті. Сонымен қатар Қазақ хандығы үш жүздің қалыптасуы өндіргіш күштердің дамуы көшпелі аудан мен отырықшыегінші аудандар арасындағы интеграция нәтижесінде өмірге келді. Олай болса Қазақстан аумағында біртұтас мемлекеттік Алтын Ордаға формальды түрде бағынып дербес мемлекет ретінде өмір сүрген Ақ Ордадан бастау алып...
74098. Ыбырай Алтынсарин 24.63 KB
  Ыбырай 1841 жылы қазан айының 20сында қазіргі Қостанай облысы Қостанай ауданында дүниеге келеді. Сөйтіп немересі кішкентай Ыбырайды Орынборда ашылады деп күтілген орысқазақ мектебіне күні бұрын жаздырып қояды. Атаң мұнда анаңмен есенаман Сүйіп сәлем жазады бүгін саған.
74099. Скиф-сақ әлеміндегі қоғамдық ұйымдар 24.17 KB
  I мыңжылдықтың басы сақ қоғамындағы алғашқы рулық қатынастар ыдырап жаңа әлеуметтік құрылымның қалыптасу үрдісінің жедел жүруімен сипатгалады. Сол кездің өзіндеақ алғашқы ірі қоғамдық еңбек бөлінісінен мыс пен қола металлургиясының тууы мен дамуынан кейін алғашында үлкен патриархаттық ал одан кейін шағын және моногамиялы отбасылар окшаулана бастады. Археологиялық деректер жеке адамдық ал кейін барып отбасылық меншіктің шыққанын айқын көрсетеді. II мыңжылдыктың аяғында және I мыңжылдыктың басында қыш ыдыстар мен кейбір қола заттарға...
74100. Қаңлы мемлекеті 22.17 KB
  II ғасырдың екінші жартысында ЧжанЦянь Қаңлы жерлерінің оңтүстігінде юечжиге ал солтүстігінде ғұндарға тәуелді екенін айтса біздің заманымыздағы I ғасырда мұндағы жағдай өзгереді. Егер Чжан Цянь юечжи әскерін 100200 мың ал қаңлы әскерін 90 мың деп хабарлаған болса ЦаньХаньШу енді қаңлы әскерін 120 мың юечжи әскерін 100 мың дейді14. Бұл кезенде Орта Азиядағы қос өзен аралығында юечжилердің негізгі бөлігінің оңтүстікке сол жағалаудағы Бактрияға ығысуы жерге отырықшылық орын алып жекежеке бес иелікке бөлінгенін мұның өзі қаңлымен...