Опубликован: 04.05.2010 | Уровень: для всех | Доступ: платный
Лекция 8:

Проектирование баз данных и работа с ними Веб-приложений. LINQ, ADO.NET Entities, DDD

Аннотация: В данной лекции дается введение в технологии доступа к данным Linq, Linq to SQL, ADO .NET Entity Framework и технологию Data Driven Development (DDD).

Презентацию к данной лекции Вы можете скачать здесь.

10.1. Доступ к данным в .NET

Продолжим рассмотрение технологий .NET для доступа к данным. В данной лекции рассмотрим технологии LINQ и LINQ to SQL, а также ADO.NET Entity Framework.

10.1.1. LINQ

10.1.1.1. Общие сведения

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

LINQ (Language-Integrated Query) представляет собой набор функций Visual Studio 2008, расширяющих мощные возможности запроса в синтаксисе языка C# и Visual Basic. LINQ представляет стандартные шаблоны для создания запросов и обновления данных; технология может быть расширена для поддержки потенциально любого типа хранилища данных [2]. 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 показана на рис. 10.1 [3].

Архитектура LINQ

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

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

var contacts =
	from c in customers
	where c.City == "Москва"
	select new { c.Name, c.Phone };

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

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

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

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

Используя некоторые новые особенности языка, LINQ позволяет использовать SQL-подобный синтаксис непосредственно в коде программы, написанной, например, на языке C#:

  1. Анонимные типы;
  2. Методы расширения;
  3. Лямбда-исчисление;
  4. Дерево выражений;
  5. Стандартные операторы языка запросов.

Рассмотрим подробнее основные принципы работы с LINQ на C# [2, 3, 4, 5, 6, 7, 8].

10.1.1.2. Введение в запросы LINQ
Примечание.В некоторых примерах далее используется база данных "Northwind". Ее можно скачать в Центре загрузки Майкрософт.

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

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

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

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

Источник: Введение в запросы LINQ [2]

Рассмотрим подробнее действия в LINQ.

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

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

Для запрашиваемого типа, выступающего в качестве источника данных 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");
10.1.1.2.2. Запрос

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

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

10.1.1.2.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 также кэширует все данные в одной коллекции объектов.

Никита Мухортов
Никита Мухортов
Россия, г. Санкт-Петербург
Андрей Деев
Андрей Деев
Россия, Артем, ДВГТУ, 2001