Проектирование баз данных и работа с ними Веб-приложений. LINQ, ADO.NET Entities, DDD
10.1.1.7. Синтаксис запроса или синтаксис метода
В предыдущих примерах большинство запросов написаны как выражения запросов с помощью декларативного синтаксиса запроса, представленного в C# 3.0. Однако в самой общеязыковой среде выполнения (CLR) .NET отсутствует понятие синтаксиса запроса. Таким образом, во время компиляции выражения запроса преобразуются в то, что понятно CLR – вызовы методов. Эти методы называются стандартными операторами запросов, и они имеют такие имена, как Where , Select , GroupBy , Join , Max , Average и т. д. Их можно вызывать непосредственно, используя синтаксис методов вместо синтаксиса запросов.
В целом, рекомендуется синтаксис запросов, так как обычно он более прост и легко читается; однако между синтаксисом методов и синтаксисом запросов нет семантической разницы. Кроме того, некоторые запросы, например такие, которые извлекают количество элементов, соответствующих указанному условию, или которые извлекают элемент, имеющий максимальное значение в исходной последовательности, могут быть выражены только в виде вызовов методов. В справочной документации по стандартным операторам запросов в пространстве имен System.Linq обычно используется синтаксис методов. Поэтому, даже на начальном этапе написания запросов LINQ полезно знать, как использовать синтаксис методов в запросах и самих выражениях запроса.
10.1.1.7.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>)>).
10.1.1.7.2. Лямбда-выражения
Обратите внимание, что в предыдущем примере условное выражение ( num % 2 == 0 ) передается в качестве встроенного аргумента методу Where: Where(num => num % 2 == 0). Это встроенное выражение называется лямбда-выражением. Оно является удобным способом написания кода, который в противном случае пришлось бы записывать в более громоздкой форме как анонимный метод, универсальный делегат или дерево выражений. В C# => является лямбда-оператором, который читается как "переходит". num слева от оператора является входной переменной, которая соответствует num в выражении запроса. Компилятор может определить тип num, так как ему известно, что numbers является универсальным типом IEnumerable<(Of <(T>)>). Основная часть лямбда-выражения представляет то же самое, что и выражение в синтаксисе запроса или в любом другом выражении или операторе C#; она может включать вызовы методов и дру гую сложную логику. Возвращаемым значением является просто результат выражения.
Приступая к работе с LINQ, нет необходимости широко использовать лямбда-выражения. Однако некоторые запросы могут выражаться только в синтаксисе методов, а некоторые из них требуют лямбда-выражений. После знакомства с лямбда-выражениями станет понятно, что они являются мощными и гибкими элементами в панели элементов LINQ.
10.1.1.7.3. Возможность компоновки запросов
Обратите внимание, что в предыдущем примере метод OrderBy вызывался от объекта, который являлся результатом вызова метода Where. Where создает отфильтрованную последовательность, а затем Orderby работает с ней, сортируя ее. Поскольку запросы возвращают IEnumerable, их можно компоновать в синтаксисе методов, объединяя вызовы методов в цепочки. При использовании синтаксиса запросов эти действия выполняет компилятор. Поскольку переменная запроса не сохраняет результаты запроса, ее можно изменить или в любое время использовать в качестве основы для нового запроса, даже после ее выполнения.
10.1.2. LINQ to SQL
10.1.2.1. Общие сведения
После ознакомления с основными аспектами работы с запросами в C# можно рассмотреть конкретный тип поставщика LINQ – LINQ to SQL.
Отображение реляционных данных на объектную модель всегда было одной из наиболее сложных проблем при построении объектно-ориентированных систем [3]. В большинстве случаев, запросы к базе данных пишутся на языке SQL, а их результат конвертируется в объекты (рис. 10.8). Разработчик вынужден одновременно работать с двумя различными представлениями данных, что значительно увеличивает трудозатраты на создание и поддержку программного продукта и увеличивает вероятность ошибок.
LINQ to SQL – простая, но достаточно мощная система объектно-реляционного отображения (ORM). По сравнению с традиционной технологией ADO.NET применение LINQ to SQL позволяет значительно упростить код, снизить вероятность ошибок и сократить время разработки проекта (рис. 10.9). Наибольший выигрыш при этом получат разработчики Web приложений, для которых, в новой версии ASP.NET предусмотрен специальный источник данных, позволяющий делать запросы непосредственно в Web странице.
10.1.2.2. Возможности LINQ to SQL
LINQ to SQL поддерживает все основные возможности, необходимые для разработчиков на SQL [8]. Можно запрашивать данные, вставлять, обновлять и удалять сведения из таблиц.
10.1.2.2.1. Выбор
Выборка (проекция) достигается написанием запроса LINQ на выбранном языке программирования и последующим выполнением этого запроса для получения результатов. Средства LINQ to SQL сами преобразуют все необходимые операции в требуемые операции SQL, которые вам знакомы.
В следующем примере извлекаются названий компаний клиентов из Москвы, которые затем отображаются в окне консоли:
// Northwnd наследуется от System.Data.Linq.DataContext Northwnd nw = new Northwnd(@"northwnd.mdf"); var companyNameQuery = from cust in nw.Customers where cust.City == "Москва" select cust.CompanyName; foreach (var customer in companyNameQuery) { Console.WriteLine(customer); }
10.1.2.2.2. Вставка
Для выполнения SQL Insert нужно добавить объекты в созданную объектную модель и вызвать метод SubmitChanges в DataContext. В следующем примере новый клиент и сведения о нем добавляются в таблицу Customers с помощью метода InsertOnSubmit:
// Northwnd наследуется от System.Data.Linq.DataContext. Northwnd nw = new Northwnd(@"northwnd.mdf"); Customer cust = new Customer(); cust.CompanyName = "Компания"; cust.City = "Москва"; cust.CustomerID = "98128"; cust.PostalCode = "55555"; cust.Phone = "555-55-55"; nw.Customers.InsertOnSubmit(cust); // В данном месте новый объект Customer добавлен в объектную модель. // В LINQ to SQL изменение не будет записано в базу данных, пока не будет вызван метод SubmitChanges. nw.SubmitChanges();
10.1.2.2.3. Обновление
Чтобы сделать Update записи в базе данных, сначала следует извлечь элемент и изменить его непосредственно в объектной модели. После изменения объекта необходимо вызвать метод SubmitChanges в DataContext, чтобы обновить базу данных.
В следующем примере извлекаются все клиенты из Москвы. Затем название города меняется с "Москва" на "Москва – Химки". Наконец, вызывается метод SubmitChanges для отправления изменений в базу данных:
Northwnd nw = new Northwnd(@"northwnd.mdf"); var cityNameQuery = from cust in nw.Customers where cust.City.Contains("Москва") select cust; foreach (var customer in cityNameQuery) { if (customer.City == "Москва") { customer.City = "Москва – Химки"; } } nw.SubmitChanges();
10.1.2.2.4. Удаление
Чтобы выполнить операцию Delete для элемента, необходимо удалить его из коллекции, в которую он входит, а затем вызвать метод SubmitChanges в DataContext, чтобы применить изменение.
В следующем примере из базы данных извлекается клиент, CustomerID которого равен 98128. Затем, после подтверждения извлечения строки клиента, вызывается метод DeleteOnSubmit, необходимый для удаления объекта из коллекции. Наконец, вызывается метод SubmitChanges для передачи удаления в базу данных:
Northwnd nw = new Northwnd(@"northwnd.mdf"); var deleteIndivCust = from cust in nw.Customers where cust.CustomerID == "98128" select cust; if (deleteIndivCust.Count() > 0) { nw.Customers.DeleteOnSubmit(deleteIndivCust.First()); nw.SubmitChanges(); }