Разработка сущностной модели данных с помощью Entity Framework

ОГЛАВЛЕНИЕ

Entity Framework — это новая технология, разработанная для ADO.NET. Она позволяет разработчикам визуализировать данные, используя логическую, а не физическую модель, благодаря чему обеспечивается определенная гибкость разработки. В июльском номере журнала за 2007 год в рубрике «Точки данных» мы давали подробный обзор технологии Entity Framework (она должна быть официально выпущена в первой половине 2008 года).

В основе Entity Framework лежит сущностная модель данных (EDM). В модели EDM определяются типы сущностей, отношения и контейнеры, а разработчик взаимодействует со всем этим посредством кода. Платформа Entity Framework строит соответствия между упомянутыми элементами и схемой хранения, которую предоставляет реляционная база данных. Модель EDM платформа Entity Framework использует через XML, в котором определяется концептуальная модель приложения. Определяться она может как самостоятельно, так и вместе с кодом XML, определяющим схему хранилищ, и с кодом XML, определяющим соответствия между ними. Код XML можно (а иногда и нужно) изменять вручную, однако гораздо проще корректировать сущностную модель и сопоставления в новом визуальном средстве разработки сущностных моделей данных.

В этом месяце мы расскажем о том, как разработать сущностную модель данных в новом средстве проектирования EDM и как изменять код XML, определяющий и модель, и сопоставления. Начнем мы с описания модулей, взаимодействующих внутри платформы Entity Framework (в том числе LINQ), а затем коснемся возможностей применения EDM. Кроме этого, мы продемонстрируем этапы создания сущностной модели и сопоставлений в визуальном конструкторе. И наконец, мы рассмотрим несколько окон, которые используются при исследовании и корректировке модели и сопоставлений.

В этой статье мы попытаеся разъяснить роли различных компонентов модели EDM, таких как EntityType и Association. Примеры, которые мы рассмотрим, иллюстрируют создание базовых сущностей. Обратите внимание, что во всех примерах используется Visual Studio® 2008 и соответствующая бета-версия 3 платформы Entity Framework (они устанавливаются отдельно). База данных, которую мы будем использовать, — это пересмотренная база данных Northwind. Она прилагается к бета-версии 3.


Что такое EDM

Прежде чем углубляться в создание и управление сущностной моделью, расскажем вкратце, что же такое модель EDM и как она взаимодействует с остальными составляющими платформы Entity Framework. Платформа Entity Framework состоит из множества компонентов. К ним, в частности, относится спецификация EDM и связанные с ней сопоставления, интерфейсы API, взаимодействующие с моделью EDM, и средства, помогающие при определении и управлении сущностной моделью и сопоставлениями. После создания сущностной модели можно писать на ее основе код, используя различные интерфейсы API, например поставщик EntityClient или службы объектов (в том числе LINQ to Entities).

Поставщик данных EntityClient схож с традиционными объектами ADO.NET. Он использует объекты EntityConnection и EntityCommand и возвращает DbDataReader. Команды для поставщика EntityClient пишутся с использованием языка Entity SQL, который напоминает T-SQL, но работает не с объектами-базами данных, а с сущностями, определенными в модели, и с объектами, материализованными при помощи служб объектов. Службы объектов можно использовать для взаимодействия с моделью EDM — либо при помощи Entity SQL, либо при помощи LINQ to Entities. Службы объектов позволяют разработчикам воспользоваться построенными на основе концептуальной модели классами, предлагающими возможности наподобие строго типизированных объектов и сохранения (см. рис. 1).

 

Рис. 1 Обзор Entity Framework 

Такие методы доступа к данным дают возможность взаимодействовать с концептуальными сущностями, определенными в сущностной модели, а не с объектами физического хранилища (к примеру реляционной базы данных). Модель данных и связанные с ней сопоставления создаются либо при помощи визуального конструктора, либо вручную, — путем изменения кода XML, содержащего определения. Платформа Entity Framework, показанная на рис. 2, обеспечивает связь между приложением и базой данных. EDM используется для описания бизнес-сущностей через концептуальную сущностную модель, а платформа Entity Framework (посредством настройки сопоставлений) переводит их в физическую форму, то есть в таблицы, представления, функции и процедуры базы данных.

 

Рис. 2 Entity Framework подключает приложение к своей базы данных

Сущностная модель приложения описывается на языке CSDL. CSDL — это язык формата XML, в котором определяются сущности и связи между ними. Разработчик взаимодействует с ними через интерфейс API, например LINQ to Entities. В платформе используется также язык SSDL — язык формата XML, используемый для определения схем хранилищ реляционных баз данных, — и язык MSL, используемый для задания соответствий между сущностями CSDL и схемой хранилищ, описанной на языке SSDL.

В CSDL разработчик имеет наибольшую свободу действий, поскольку именно здесь определяются сущности, с которыми ему чаще всего и приходится работать. На рис. 2 показано, что одни сущности могут сопоставляться отдельным таблицам в базе данных, а другие — целой группе таблиц. Сопоставление сущностей формируется группой разработчиков на основе бизнес-модели. Она зачастую строится вокруг одной единственной сущности, которая представляется несколькими физическими таблицами базы данных. На рис. 2 также показано, что сущность может сопоставляться представлению базы данных и может принимать метод, вызывающие хранимые процедуры. Сущности могут выводиться из других сущностей с использованием схемы наследования, существующей в концептуальной модели. Это лишь несколько способов формирования сущностной модели, доступных в средствах проектирования EDM.


Построение EDM при помощи мастера

Создание сущностной модели начинается с добавления в проект ADO. NET нового файла EDM (см. <Fig>рис. 3</Fig>). После этого мастер EDM предложит либо создать модель на основе базы данных, либо начать с пустой модели. Создать модель на основе базы данных — неплохое решение, если доступ к базе данных у вас уже есть. Некоторые методики разработки, к примеру проектирование на основе областей, предполагают создание сущностной модели области до проектирования базы. В таком случае удобнее начать с пустой модели, а затем наполнить ее сущностями в визуальном конструкторе EDM. В качестве примера мы построили сущностную модель на основе базы данных Northwind.

 

Рис. 3 Добавление EDM-файла к проекту

В следующем окне мастер просит ввести информацию, необходимую для подключения к базе данных. Затем предлагается указать, какие объекты базы данных нужно включить в модель. Как видно из рис. 4, мы переносим в модель все таблицы и все хранимые процедуры базы данных Northwind, за исключением тех, которые используются для построения диаграмм. Таблицы изначально напрямую сопоставляются с сущностями, а хранимые процедуры можно сопоставить методам созданного контейнера.

 

Рис. 4 Выбор объектов базы данных 

После выбора объектов, включаемых в модель, мастер EDM создает файл EDMX, в котором определяется модель и сопоставления, и добавляет в проект ссылки в соответствии с требованиями платформы Entity Framework. Файл EDMX — это файл формата XML, состоящий из четырех основных разделов: сведений о визуальном представлении концептуальной модели в программе-конструкторе, описания концептуальной модели на языке CSDL, описания слоя сопоставлений на языке MSL о писания физической модели на языке SSDL. Вся эта информация записывается в один файл.

Данные, хранящиеся в файле EDMX, используются в Visual Studio для создания послойного представления модели в программе-конструкторе. Делается это только в процессе проектирования. Коды CSDL, MSL и SSDL используются в момент компиляции для создания классов, которыми будет представлена модель EDM.


Хранимые процедуры в сущностной модели

Во время создания модели мы добавили в нее хранимые процедуры, однако включены они были только в определение SSDL. Хранимые процедуры могут взаимодействовать с самыми различными таблицами, а также с другими объектами базы данных, поэтому платформа Entity Framework не берется автоматически сопоставить хранимую процедуру какой-то конкретной сущности в определении CSDL. Для создания метода, порождающего сопоставления для хранимой процедуры, нужно отредактировать код XML в файле EDMX.

Чтобы создать метод, который будет возвращать сущности данного типа, нам сначала нужно выбрать хранимую процедуру. В качестве примера мы добавим метод GetTenMostExpensiveProducts. Он будет вызывать хранимую процедуру Ten Most Expensive Products и возвращать объекты типа Product. Поскольку в коде SSDL хранимая процедура уже определена как элемент Function, нам нужно сделать следующий шаг — добавить метод в код CSDL. Для этого нужно добавить элемент FunctionImport в EntityContainer в качестве дочернего элемента:

<FunctionImport Name="GetTenMostExpensiveProducts"
  EntitySet="Products"
  ReturnType="Collection(Self.Products)">
</FunctionImport>

В атрибуте Name указывается имя метода для сущностного контейнера. В атрибуте EntitySet указывается набор EntitySet. Атрибут ReturnType ссылается на тип возвращаемых сущностей EntityType (или коллекцию значение EntityTypes, как в нашем случае).

Ссылка на Self в данном примере — это псевдоним, указывающий на текущее пространство имен, то есть на NWModel. Обратите внимание, что здесь можно было поставить как Self, так и NWModel.

Если для работы метода требуются параметры, их можно добавить при помощи тега <Parameter>. Например, если бы в хранимой процедуре был параметр CategoryId и мы бы решили его включить в метод, мы бы добавили в элемент FunctionImport следующий фрагмент кода XML:

<Parameter Name="CategoryId" Type="Int32" Mode="in"/>

Но в нашем примере параметры не используются, поэтому мы просто пропустим этот шаг.

Итак, в коде CSDL определен метод, тип сущностей, который он возвращает, и набор EntitySet, к которому должны принадлежать возвращаемые сущности, а в коде SSDL определена хранимая процедура. Теперь необходимо построить сопоставление между CSDL и SSDL, чтобы концептуальный метод знал, какую хранимую процедуру он должна выполнять. Сопоставление выполняется путем добавления следующего фрагмента кода (FunctionImportMapping) в раздел EntityContainerMapping в определении MSL:

<FunctionImportMapping 
  FunctionImportName="GetTenMostExpensiveProducts" 
  FunctionName="NWModel.Store.Ten_Most_Expensive_Products"/>

Здесь элемент FunctionImportMapping использует атрибут FunctionName — с его помощью он ссылается на полное имя элемента Function, определенного в SSDL. Элемент FunctionImportName ссылается на имя элемента FunctionImport, определенного в CSDL.


Окна, используемые при работе с EDM

После того, как модель EDM спроектирована и построена, можно контролировать ее состояние при помощи различных окон. Окно «Представление классов» (см. рис. 5), которое существовало и раньше, помогает определить, какие объекты имеются в распоряжении разработчика. Например, в нем будет отображаться созданный нами метод GetTenMostExpensiveProducts для класса EntityContainer в модели NWEntities, а также все классы, имеющиеся в пространстве имен NWModel. В этот список входит по одному классу на каждый тип EntityType в коде CSDL плюс один класс для всей модели NWEntities.

 

Рис. 5 EDM в представлении классов 

В платформе также появилось несколько новых окон, предоставляющих сведения о сущностной модели и связанных с ней сопоставлениях. К ним относится конструктор EDM, окно сведений о сопоставлениях сущностей и окно просмотра сущностной модели.

В окне просмотра сущностной модели содержатся все компоненты CSDL и SSDL, в том числе EntityTypes, Associations, EntitySets, AssociationsSets и Function Imports (компоненты CSDL), а также все элементы SSDL.

В конструкторе EDM (см. рис. 6) показывается схема модели, созданной разработчиком. Здесь все элементы концептуальной модели представлены наглядным образом. Разработчик может просматривать модель и управлять ею. Обратите внимание на сущность Product, показанную на рис. 6 — она соответствует типу Product EntityType в CSDL. Каждый тип EntityType имеет список свойств — как скалярных, так и навигационных.

 

Рис. 6 Конструктор сущностной модели данных

Навигационные свойства используются при переходе по связям CSDL. Навигационные свойства становятся общими свойствами соответствующего класса EntityType и используются для ссылки на другую сущность или набор сущностей, связанный с исходной сущностью. Допустим, тип EntityType Product имеет навигационное свойство Categories, которое ссылается на сущность Category, относящуюся к конкретному экземпляру сущности Product. Тип EntityType Category, в свою очередь, имеет навигационное свойство Products. Существует оно для того, чтобы любой экземпляр сущности Category можно было связать с сущностями Product.

Конструктор позволяет добавлять, изменять и удалять типы сущностей, связи, скалярные и навигационные свойства. К примеру, можно привести имена всех типов EntityTypes к формам единственного числа — многие разработчики при именовании сущностей используют их как раз в таком виде. Для этого нужно щелкнуть имя типа EntityType и изменить его. Также имя типа EntityType можно изменить в окне свойств. В качестве примера мы привели имена всех типов EntityTypes к формам единственного числа — это можно видеть на рис. 6. После этого нам пришлось исправить код XML, в который мы до этого добавляли вызов хранимой процедуры в качестве метода для NWEntities EntityContainer. Это очень просто, нужно только обновить код таким образом, чтобы в нем упоминался тип EntityType Product (а не Products):

<FunctionImport Name="GetTenMostExpensiveProducts"
  EntitySet="Products"
  ReturnType="Collection(Self.Product)">
</FunctionImport>

Тут можно сделать немаловажный вывод о том, что основным элементам, таким как EntityType, EntityContainer и Association, имена лучше давать до изменения модели данных. Это сведет к минимуму число изменений, которые нужно будет вносить в код вручную. Это касается также того кода, который уже когда-то создавался на основании изменяемой модели данных. В случае возникновения проблем окно «Список ошибок» скорее всего должно помочь, постому что в нем наверняка будут содержаться указания на те элементы, для которых имеются неверные ссылки в коде XML.

И наконец, окно свойств сопоставлений сущностей (в отличие от конструктора, показывающего концептуальную модель) дает возможность просмотреть и изменить сопоставления (код MSL) между EDM и хранилищем данных.


Производные сущности

Те же средства, что помогают при проектировании сущностной модели, позволяют ее изменять. В объектно-ориентированном программировании понятие наследования является одним из основных. Модель EDM поддерживает создание и изменение производных сущностей — как непосредственно в коде XML, так и в наглядной форме в конструкторе EDM.

Чтобы проиллюстрировать процесс разработки производных сущностей, мы создадим тип EntityType DiscontinuedProduct на основе типа EntityType Product. Тип EntityType Product имеет скалярное свойство Discontinued, принимающее значения логического типа. Его мы используем в качестве условия, определяющего принадлежность каждого отдельно взятого продукта к тому или иному типу экземпляров. В конструкторе EDM мы щелкаем правой кнопкой мыши и во всплывшем меню выбираем пункт «Добавить» > «Сущность». Затем мы вводи имя новой сущности — DiscontinuedProduct — и выбираем сущность Product в качестве базовой (см. рис. 7).

 

Рис. 7 Создание унаследованной сущности 

На следующем этапе нам нужно определить фактор различения этих сущностей, что делается при помощи условия. Мы выбираем в конструкторе тип EntityType Product и открываем окно свойств сопоставления сущностей. В столбце сопоставлений мы выбираем свойство Discontinued и удаляем сопоставление. При этом свойство Discontinued удаляется как из типа Product, так и из типа DiscontinuedProduct (выражение, участвующее в создании условия, не должно использоваться в качестве свойства). Затем мы переходим к разделу Maps to Products в окне свойств сопоставления сущностей и добавляем условие Condition of Discontinued = 0. После этого выбираем тип DiscontinuedProduct и добавляем условие Condition of Discontinued = 1.

Создание производных сущностей таким методом действительно не вызывает сложностей. Если необходимо, вы можете добавить производной сущности дополнительные свойства, которых нет у базового класса. Когда вы создаете экземпляр DiscontinuedProduct в коде .NET и сохраняете его, платформа Entity Framework в столбце Discontinued устанавливает значение 0 в соответствии с существующим условием.

Условия создаются в разделе EntityTypeMapping в коде MSL. Они фактически просто устанавливают фильтр на получение строк. При сохранении данных условия используются для того, чтобы определить, какое значение записать в столбец базы данных, основанный на производном типе.

Выводы

Средства конструирования, включенные в Visual Studio 2008, и файлы XML позволяют создать сущностную модель данных, в которой будет использоваться наследование и применяться хранимые процедуры, — модель, позволяющую сосредоточиться на бизнес-процессах, вместо того чтобы писать код непосредственно на основании схемы реляционной базы данных. После того как сущностная модель полностью оформлена, взаимодействие с ней через интерфейсы API, такие как службы объектов, проблем не вызывает. Кроме того, при изменении модели данных вам не придется вносить изменения в базу данных, поскольку сопоставления позволяют обособить концептуальную модель от модели хранилищ.

Джон Папа — старший консультант по технологии .NET в компании ASPSOFT и страстный поклонник бейсбола, который почти все летние вечера проводит, болея за «Янки» со своим семейством и верным псом Кади. Джон, имея звание MVP по C#, является автором нескольких книг по ADO, XML и SQL Server.