Обзор ADO.NET Entity Framework

ОГЛАВЛЕНИЕ

В выпуске Visual Studio 2008 в ADO.NET представлена новая архитектура Entity Framework. Она позволяет разработчикам обращаться к данным, используя объектную модель вместо логической или реляционной модели данных. Entity Framework помогает абстрагировать логическую схему данных в концептуальную модель и обеспечивает несколько способов взаимодействия с концептуальной моделью через службы Object Services и нового поставщика данных, называющегося EntityClient. В статье этого месяца обсуждается, что такое архитектура Entity Framework, как она применяется в приложении и как с ее учетом разрабатывать и программировать.

Entity Framework представляет логическую структуру базы данных, используя концептуальный слой, слой сопоставления и логический слой. В этой статье я рассмотрю задачи каждого из этих слоев. Я также представлю поставщика данных EntityClient и новый язык, Entity SQL, который может взаимодействовать с сущностной моделью данных (Entity Data Model, EDM) концептуального слоя.

Альтернативой поставщику данных EntityClient являются службы Object Services. Конкретнее, Object Services в Entity Framework могут помочь уменьшить количество кода для доступа к данным, который приходится писать разработчикам. Я также обсуждаю и показываю, как использовать Object Services совместно с Entity SQL и LINQ (запрос, встраиваемый в язык) для Entities для взаимодействия с EDM и извлечения концептуальных сущностей.


Компоненты Entity Framework

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

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

 

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

Такие способы доступа к данным позволяют разработчику взаимодействовать с концептуальными сущностями EDM. Слои EDM существуют в виде XML-файлов; на сегодня EDM можно создать вручную при помощи средства командной строки (EDMGEN.EXE) или при помощи мастера Visual Studio.


Сущностная модель данных

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

  • Концептуальный слой
  • Слой сопоставления
  • Логический слой

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

 

Рис. 2 Разработка сущностной модели данных

Концептуальная модель определяется в XML-файле с использованием языка определения концептуальной схемы (Conceptual Schema Definition Language, CSDL). CSDL определяет сущности и взаимоотношения в том виде, в каком они представлены в бизнес-слое приложения. Логическая модель, представляющая схему базы данных, определяется в XML-файле с использованием языка определения схемы хранилища (Store Schema Definition Language, SSDL). Например, в концептуальной модели может быть сущность, на самом деле получающая данные из нескольких таблиц базы данных. Концептуальная и логическая модели могут связывать сущности взаимно-однозначно. Однако преимущество EDM в том, что это не обязательно. Слой сопоставления, определяемый с использованием языка схемы сопоставления (Mapping Schema Language, MSL), сопоставляет два остальных слоя друг другу. Это сопоставление и позволяет разработчикам программировать для концептуальной модели и сопоставлять инструкции логической модели.


Создание сущностной модели данных

Вы можете создать EDM, используя базу данных в качестве отправной точки. Затем можно изменить XML вручную (или, возможно, при помощи средства моделирования, которое может быть доступно в следующем выпуске Visual Studio). Когда вы добавляете к вашему проекту EDM ADO.NET, мастер проходит процесс создания EDM по шагам.

После того как вы выбрали для создания EDM тестовую базу данных Northwind, поставляемую с «Orcas», вам предоставляется список объектов, которые возможно смоделировать, как показано на рис. 3. Я выбрал вариант включить все таблицы в модель, поэтому мастер создает файлы CSDL, SSDL и MSL для всех таблиц в Northwind. Конечно, это взаимно-однозначное сопоставление таблиц сущностям. Я могу изменить сопоставление для соответствия моим бизнес-нуждам, комбинируя сущности или используя наследование.

 

Рис. 3 Мастер EDM

Мастер также создает набор классов, основываясь на CSDL, представляющем модель. Часть этих классов показана на рис. 4 в окне просмотра классов. Из-за того, что я начал со взаимно-однозначного сопоставления, для каждой таблицы есть по классу. Мастер EDM отследил связь между таблицами Customers и Orders в базе данных и создал соответствующую ассоциацию в концептуальной модели. Поэтому класс Customers содержит свойство перемещения Orders, позволяющее разработчикам переходить от экземпляра класса Customers к любому из экземпляров класса Orders для него.

 

Рис. 4 Классы 


Разбор CSDL

Метаданные, содержащиеся в CSDL-файле, содержат списки сущностей, представленных элементами EntityType, и связей, представленных элементами Association, относящихся к типу AssociationType (обратите внимание, что в схеме именования в выпуске Beta 1 есть несоответствия). На рис. 5 показан фрагмент CSDL-файла, который я создал ранее. Сущности содержат списки скалярных свойств, определяющих их. Атрибут Key показывает, какие свойства являются ключевыми. Составные ключи выделяются разделением имен свойств пробелами. Сущности также могут содержать специальные свойства, называемые NavigationProperty. Они определяют переходы от одной сущности к другой через ассоциации.

Figure 5 Определение EntityType в CSDL

<EntityType Name=”Customers” Key=”CustomerID”>
  <Property Name=”CustomerID” Type=”String” Nullable=”false” 
MaxLength=”4000” FixedLength=”true” />
  <Property Name=”CompanyName” Type=”String” Nullable=”false” 
MaxLength=”4000” />
  <Property Name=”ContactName” Type=”String” MaxLength=”4000” />
  <Property Name=”ContactTitle” Type=”String” MaxLength=”4000” />
  <Property Name=”Address” Type=”String” MaxLength=”4000” />
  <Property Name=”City” Type=”String” MaxLength=”4000” />
  <Property Name=”Region” Type=”String” MaxLength=”4000” />
  <Property Name=”PostalCode” Type=”String” MaxLength=”4000” />
  <Property Name=”Country” Type=”String” MaxLength=”4000” />
  <Property Name=”Phone” Type=”String” MaxLength=”4000” />
  <Property Name=”Fax” Type=”String” MaxLength=”4000” />
  <NavigationProperty Name=”Orders” 
Relationship=”NorthwindModel.FK_Orders_Customers” 
FromRole=”Customers” ToRole=”Orders” />
  </EntityType>

В следующем фрагменте CSDL определятся AssociationType между Customer и относящимися к нему Orders:

  <Association Name=”FK_Orders_Customers”>
  <End Role=”Customers” Type=
  ”NorthwindModel.Customers”  
  Multiplicity=”0..1” />
  <End Role=”Orders” Type=
  ”NorthwindModel.Orders” Multiplicity=”*” />
  </Association>

Элементы End в AssociationType указывают участвующих в ассоциации. В этом примере сущность Customers ассоциирована с сущностью Orders. Также сущность Customers может быть связана с любым числом сущностей Orders, что определяется атрибутом Multiplicity.

В то время как элементы EntityType и AssociationType определяют типы сущностей области и отношения между ними, элементы EntitySet и AssociationSet определяют их области применения. Все «наборы», которые должны быть сгруппированы вместе, содержатся внутри элемента EntityContainer. (Полный CSDL приведен в файле NorthwindEntities.csdl в прилагающейся загрузке).

Следующий фрагмент CSDL демонстрирует EntityContainer и часть его содержимого:

<EntityContainer Name=”NorthwindEntities”>
  <EntitySet Name=”Customers” 
  EntityType=”NorthwindModel.Customers” />
  <EntitySet Name=”Orders” 
  EntityType=”NorthwindModel.Orders” />
  <AssociationSet Name=”FK_Orders_Customers” 
  Association=”NorthwindModel.FK_Orders_Customers”>
  <End Role=”Customers” EntitySet=”Customers” />
  <End Role=”Orders” EntitySet=”Orders” />
  </AssociationSet>
</EntityContainer>

Этот фрагмент включает наборы EntitySet для типов EntityType Customers и Orders: Здесь же объявляется ассоциация AssociationSet FK_Orders_Customers. Таким образом, этот фрагмент определяет сущности Customers и Orders, а также связь между ними.


Сопоставление хранилищу

Файл SSDL определяет структуру реляционных данных в базе данных. В нем также используются элементы XML EntityType и AssociationType, в этом случае для объявления структур таблиц и существующих в базе данных внешних ключей соответственно. Пространство имен файла SSDL основано на имени базы данных, использованной в EDM, тогда как элемент EntityContainer назван в соответствии со схемой базы данных. EntityContainer содержит наборы элементов EntitySet и AssociationSet, которые объявляют экземпляры таблиц и отношений, представленных элементами EntityType и AssociationType. Каждому набору EntitySet в файле SSDL соответствует таблица в базе данных.

Если вы создадите EDM на основе базы данных и немедленно откроете файлы CSDL и SSDL, не внося изменений, вы увидите, что они удивительно похожи. Это потому, что модели созданы прямо из базы данных, и концептуальная модель сопоставляется логическому хранилищу напрямую. Файл MSL содержит прямое соответствие CSDL и SSDL. Все запросы на основе такой EDM транслируются в созданные команды SQL. Entity Framework также поддерживает использование хранимых процедур вместо создания запросов SQL.

Для сопоставления модели (CSDL) хранилищу (SSDL) используется элемент EntityContainerMapping. Атрибут StorageEntityContainer показывает название контейнера EntityContainer в хранилище, а атрибут EdmEntityContainer показывает соответствующий EntityContainer в модели. Для сопоставления набора EntitySet модели набору EntitySet хранилища требуется элемент EntitySetMapping. Атрибут Name определяет название набора EntitySet модели, а атрибут TableName определяет название соответствующего набора EntitySet в хранилище. Каждое свойство модели сопоставляется хранилищу посредством элемента ScalarProperty. На Рис. 6 показан фрагмент файла MSL.

Figure 6 Сопоставление унаследованной сущности в MSL

<cs:EntitySetMapping cs:Name=”Products”>
  <cs:EntityTypeMapping cs:TypeName=”NorthwindModel.Products”>
  <cs:TableMappingFragment cs:TableName=”Products”>
  <cs:ScalarProperty cs:Name=”ProductID” cs:ColumnName=”ProductID” />
  <cs:ScalarProperty cs:Name=”ProductName” 
  cs:ColumnName=”ProductName” />
  <cs:ScalarProperty cs:Name=”QuantityPerUnit” 
  cs:ColumnName=”QuantityPerUnit” />
  <cs:ScalarProperty cs:Name=”UnitPrice” cs:ColumnName=”UnitPrice” />
  <cs:ScalarProperty cs:Name=”UnitsInStock” 
  cs:ColumnName=”UnitsInStock” />
  <cs:ScalarProperty cs:Name=”UnitsOnOrder” 
  cs:ColumnName=”UnitsOnOrder” />
  <cs:ScalarProperty cs:Name=”ReorderLevel” 
  cs:ColumnName=”ReorderLevel” />
  <!--<cs:ScalarProperty cs:Name=”Discontinued” 
  cs:ColumnName=”Discontinued” />-->
  <cs:Condition cs:ColumnName=”Discontinued” cs:Value=”0”/>
  </cs:TableMappingFragment>
  </cs:EntityTypeMapping>
<!--</cs:EntitySetMapping>
<cs:EntitySetMapping cs:Name=”DiscontinuedProducts”>-->
  <cs:EntityTypeMapping cs:TypeName=
  ”NorthwindModel.DiscontinuedProducts”>
  <cs:TableMappingFragment cs:TableName=”Products”>
  <cs:ScalarProperty cs:Name=”ProductID” cs:ColumnName=”ProductID” />
  <cs:ScalarProperty cs:Name=”ProductName” 
  cs:ColumnName=”ProductName” />
  <cs:ScalarProperty cs:Name=”QuantityPerUnit” 
  cs:ColumnName=”QuantityPerUnit” />
  <cs:ScalarProperty cs:Name=”UnitPrice” cs:ColumnName=”UnitPrice” />
  <cs:ScalarProperty cs:Name=”UnitsInStock” 
  cs:ColumnName=”UnitsInStock” />
  <cs:ScalarProperty cs:Name=”UnitsOnOrder” 
  cs:ColumnName=”UnitsOnOrder” />
  <cs:ScalarProperty cs:Name=”ReorderLevel” 
  cs:ColumnName=”ReorderLevel” />
  <cs:Condition cs:ColumnName=”Discontinued” cs:Value=”1”/>
  </cs:TableMappingFragment>
  </cs:EntityTypeMapping>
</cs:EntitySetMapping>


Определение наследования

EDM также поддерживает модели, не соответствующие базе данных взаимно-однозначно. Например, используя базу данных Northwind, вы можете создать класс, называющийся DiscontinuedProducts, который наследует все свойства класса Products, но содержит только продукты, значение поля Discontinued для которых равно 1. (Отметьте, что класс DiscontinuedProducts мог бы также иметь дополнительные свойства). Это упрощенная схема наследования, но она показывает, как применить наследование в EDM.

Первым шагом по созданию класса DiscontinuedProducts в концептуальной модели является открытие файла CSDL, создание нового типа EntityType, называющегося DiscontinuedProducts, и установка его атрибута BaseType в значение NorthwindModel.Products (схема и название базового EntityType). Порожденный тип EntityType наследует свойства EntityType Products, включая ключи. Поэтому я не указываю атрибут Key и свойства для произведенного EntityType. Я также убираю свойство Discontinued из EntityType Products. Дополнительный код CSDL для всего описанного выглядит так:

<EntityType Name=”DiscontinuedProducts” 
  BaseType=”NorthwindModel.Products”/>

Следующим шагом в этом процессе является открытие файла MSL и удаление атрибутов TypeName и TableName элемента EntitySetMapping Products. Теперь они будут устанавливаться отдельно для каждого конкретного типа EntityType. Затем необходимо создать дочерний элемент EntityTypeMapping и установить TypeName как NorthwindModel.Products. Для каждого EntityType, порожденного от базового EntityType, EntitySetMapping должен включать элемент EntityTypeMapping. Далее нужно создать дочерний элемент EntityTypeMapping, называющийся TableMappingFragment, и установить для его атрибута TableName значение Products. В целом, эти шаги переносят сопоставление с элемента EntitySetMapping на более низкий и детальный уровень.

Закомментируйте сопоставление свойства Discontinued и добавьте элемент Condition, указывающий, что включены будут только записи со значением поля Discontinued, равным 0. Скопируйте целиком XML фрагмент EntityTypeMapping, поменяйте значение атрибута Name на DiscontinuedProducts и поменяйте значение в условии на 1. Новый фрагмент файла MSL приведен на Рис. 6.


Сопоставление нескольким таблицам

Другим способом уйти в EDM от строгого взаимно-однозначного сопоставления модели хранилищу является сопоставление одной сущности в модели нескольким таблицам в хранилище. Между таблицами Contacts и ContactNameSplit в базе данных Northwind есть взаимно-однозначная связь, и можно объединить их в одну сущность в модели. Для примера я создам в модели сущность, включающую все поля таблицы Contacts и поля Title и Name из таблицы ContactNameSplit.

Первым изменением является добавление двух дополнительных свойств в элементе EntityType Contacts в файле CSDL: Name и Title.

Дальнейшие изменения более описательны. Эти два новых свойства в модели теперь должны быть сопоставлены хранилищу в файле MSL. Необходимо изменить элемент EntitySetMapping для EntitySet Contacts, чтобы представлять сопоставление нескольким таблицам. В этом примере я изменил существующий тег EntitySetMapping для EntitySet Contacts, удалив атрибуты TableName и TypeName. Эти атрибуты объявляются в элементе EntitySetMapping, только если набор EntitySet в модели взаимно-однозначно сопоставлен набору EntitySet в хранилище.

Поскольку сопоставление набора EntitySet модели набору EntitySet хранилища для Contacts было удалено, необходимо создать ему замену. Такой заменой является дочерний элемент EntityTypeMapping. Их необходимо создать два – по одному для представления каждой из таблиц Contacts и ContactNameSplit в хранилище. Элементы EntityTypeMapping определяют атрибут TypeName для каждого из наборов EntitySet.

Внутри каждого из элементов EntityTypeMapping помещен дочерний элемент, называющийся TableMappingFragment. Этот элемент включает атрибут TableName, соответствующий набору EntitySet в хранилище. В TableMappingFragment определены все элементы ScalarProperty, сопоставляющие свойства модели хранилищу. На Рис. 7 показан обновленный элемент EntitySetMapping Contacts, который теперь сопоставляет таблицы Contacts и ContactSplitName хранилища одному набору EntitySet модели.

Figure 7 Объединение таблиц в сущность

<cs:EntitySetMapping cs:Name=”Contacts”>
  <cs:EntityTypeMapping cs:TypeName=”NorthwindModel.Contacts”>
  <cs:TableMappingFragment cs:TableName=”Contacts”>
  <cs:ScalarProperty cs:Name=”ContactID” 
  cs:ColumnName=”ContactID” />
  <cs:ScalarProperty cs:Name=”ContactType” 
  cs:ColumnName=”ContactType” />
  <cs:ScalarProperty cs:Name=”CompanyName” 
  cs:ColumnName=”CompanyName” />
  <cs:ScalarProperty cs:Name=”ContactName” 
  cs:ColumnName=”ContactName” />
  <cs:ScalarProperty cs:Name=”ContactTitle” 
  cs:ColumnName=”ContactTitle” />
  <cs:ScalarProperty cs:Name=”Address” cs:ColumnName=”Address” />
  <cs:ScalarProperty cs:Name=”City” cs:ColumnName=”City” />
  <cs:ScalarProperty cs:Name=”Region” cs:ColumnName=”Region” />
  <cs:ScalarProperty cs:Name=”PostalCode” 
  cs:ColumnName=”PostalCode” />
  <cs:ScalarProperty cs:Name=”Country” cs:ColumnName=”Country” />
  <cs:ScalarProperty cs:Name=”Phone” cs:ColumnName=”Phone” />
  <cs:ScalarProperty cs:Name=”Extension” 
  cs:ColumnName=”Extension” />
  <cs:ScalarProperty cs:Name=”Fax” cs:ColumnName=”Fax” />
  <cs:ScalarProperty cs:Name=”PhotoPath” 
  cs:ColumnName=”PhotoPath” />
  </cs:TableMappingFragment>
  </cs:EntityTypeMapping>
  <cs:EntityTypeMapping cs:TypeName=”ContactNameSplit”>
  <cs:TableMappingFragment cs:TableName=”ContactNameSplit”>
  <cs:ScalarProperty cs:Name=”ContactID” cs:ColumnName=”ID” />
  <cs:ScalarProperty cs:Name=”Name” cs:ColumnName=”Name” />
  <cs:ScalarProperty cs:Name=”Title” cs:ColumnName=”Title” />
  </cs:TableMappingFragment>
  </cs:EntityTypeMapping>


Использование EntityClient

Доступ к концептуальной модели Entity Framework может быть организован тремя способами (см. Рис. 1). Здесь я представлю EntityClient, новый поставщик данных .NET.
EntityClient абстрагирован от логического хранилища, поскольку он взаимодействует с концептуальной моделью посредством своего собственного текстового языка, называющегося Entity SQL. Все запросы Entity SQL, выполняемые через EntityClient, компилируются в деревья команд, посылаемые в хранилище. Преобразование запросов Entity SQL через концептуальную модель и далее в хранилище обеспечивается Entity Framework.

Классы EntityClient похожи на классы распространенных поставщиков ADO.NET. Например, запросы EntityClient выполняются в объекте EntityCommand, которому необходим объект EntityConnection для подключения к EDM. Хотя EntityClient взаимодействует с сущностями EDM, он не возвращает экземпляры сущностей, а вместо этого возвращает все результаты в виде объекта DbDataReader. При помощи DbDataReader EntityClient может возвращать стандартный набор строк и столбцов, либо представление более сложной иерархии данных.

На Рис. 8 показан пример использования EntityClient для подключения к концептуальной модели и получения списка заказчиков из Лондона. EntityConnection может воспринимать полную строку подключения к концептуальному слою или ее название в файле App.Config. Строка подключения содержит перечень файлов метаданных (файлов CSDL, MSL и SSDL), а также строку подключения к хранилищу, зависящую от конкретной базы данных. Здесь приведен пример полной строки подключения для Рис. 8:

Figure 8 Использование Entity SQL в EntityClient для доступа к EDM

string city = “London”;
using (EntityConnection cn = 
  new EntityConnection(“Name=NorthwindEntities”))
{
  cn.Open();
  EntityCommand cmd = cn.CreateCommand();
  cmd.CommandText = 
  “SELECT VALUE c FROM NorthwindEntities.Customers “ +
  “AS c WHERE c.City = @city”;
  cmd.Parameters.AddWithValue(“city”, city);
  DbDataReader rdr = cmd.ExecuteReader(
  CommandBehavior.SequentialAccess);
  while (rdr.Read())
  Console.WriteLine(rdr[“CompanyName”].ToString());
  rdr.Close();
}

“metadata=.\NorthwindEntities.csdl|.\NorthwindEntities.ssdl|.
\NorthwindEntities.msl;provider=System.Data.SqlClient;provider connection string=’Data Source=DDVPC01
\SQLEXPRESS;Initial Catalog=Northwind;
Integrated Security=True’”

Пример кода на Рис. 8 показывает, как создать объект EntityConnection и выполнить для него команду EntityCommand. Запрос, написанный на Entity SQL, обращается к набору EntitySet Customers в EDM. Отметьте, что синтаксис специально сделан аналогичным синтаксису T-SQL. (Если вам нужен хороший справочник по синтаксису Entity SQL, воспользуйтесь документацией MSDN® для первого бета-выпуска «Orcas».)

Как показано на Рис. 8, объекты EntityParameter также могут быть добавлены. В первом бета-выпуске EntityClient не поддерживает запросов DML; однако, ведется работа на поддержкой этого в будущем. Тем не менее, DML можно выполнить другим способом, таким как LINQ to Entities.


Использование служб Object Services

Другим способом взаимодействия с данными, представленными EDM, является использование служб Object Services. Службы Object Services предоставляют возможность загружать объекты и следовать любым связям, определенным в EDM. Как показано на Рис. 1, службы Object Services используют EntityClient для получения данных. Службы Object Services добавляют разрешение идентификаторов, что при использовании DataSet приходится делать вручную. Они также обеспечивают неизменность объектов и отслеживание изменений через события, чтобы позволять явные загрузку и сохранение. За счет этого снижается число обращений к серверу.

Службы Object Services позволяют возвращать списки объектов напрямую – можно возвращать как проекции, так и определенные сущности. Например, пользуясь Object Services, можно получить List<Customers>, как определено в EDM. Объекты Customers можно просмотреть, изменить значения, а затем сохранить данные в базе данных.

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

Можно использовать службы Object Services для выполнения запросов Entity SQL или можно писать запросы, используя LINQ to Entities. Следующий пример демонстрирует запрос на Entity SQL, выполняемый через службы Object Services для получения списка заказчиков:

string city = “London”;
ObjectQuery<Customers> query = northwindContext.CreateQuery<Customers>(
  “SELECT VALUE c FROM Customers AS c WHERE c.City = @city”,
  new ObjectParameter(“city”, city));
foreach (Customers c in query) Console.WriteLine(c.CompanyName);

В EDM EntityContainer представлен классом, производным от ObjectContext (в этом примере northwindContext). Класс ObjectContext реализует интерфейс ObjectQuery<T>, позволяя создавать запросы, используя Entity SQL и LINQ.

Метод CreateQuery принимает параметризованный оператор Entity SQL, определяющей запрос, возвращающий список сущностей Customers. Собственно выражение SQL, обращающееся к базе, выполняется при итерации по ObjectQuery<Customers> оператором foreach.


Использование LINQ to Entities

С помощью служб Object Services можно выполнять динамические запросы, написанные на Entity SQL, для взаимодействия с сущностями EDM. Но в Entity Framework также можно работать со строго типизированными классами EDM, используя LINQ to Entities. Например, в только что продемонстрированном примере запрос на Entity SQL через Object Services может быть изменен для выполнения на LINQ to Entities, вот так:

string city = “London”;
var query = from c in northwindContext.Customers
  where c.City == city
  select c;
foreach (Customers c in query) Console.WriteLine(c.CompanyName);

В этом примере кода весь основанный на тексте синтаксис Entity SQL заменен на строго типизированный синтаксис LINQ, поддерживаемый в C# 3.0. Более подробная информация о LINQ и его поддержке в C# и Visual Basic® доступна в июньском выпуске MSDN Magazine за 2007 г. по адресу msdn.microsoft.com/msdnmag/issues/07/06.

Теперь вспомните, как я создал в EDM тип EntityType, называющийся DiscontinuedProducts, произведенный от Products. Возможности EDM по наследованию могут быть использованы совместно с LINQ и Object Services для получения списка продуктов, которые больше не поддерживаются. Обратите внимание, что в следующем примере не указывается, что значение поля Discontinued должно быть равно 1. Вместо этого, тип для сущности продукта сверяется с производным EntityType DiscontinuedProducts, что в свою очередь задействует условие, которое я создал в сопоставлении (файле MSL), чтобы создать подходящее выражение SQL для получения только неподдерживаемых продуктов:

var query = from p in northwindContext.Products
  where p is DiscontinuedProducts
  select p;
foreach (Products p in query) Console.WriteLine(p.ProductName);

Вы можете также создавать запросы, использующие преимущества встроенных отношений EDM (определяемых набором AssociationSet). Например, список заказов для заказчиков из Лондона может быть получен посредством следующего выражения запроса LINQ:

var query = from o in northwindContext.Orders
  where o.Customers.City == “London”
  select o;
foreach (Orders o in query) Console.WriteLine(o.OrderID);

Этот пример кода начинается с сущности Orders и использует ее свойство перемещения Customers для просмотра свойства City. Заказы от заказчиков не из Лондона полностью отфильтровываются. Поскольку возвращается список сущностей Orders, сущности могут быть изменены, и изменения могут быть сохранены в базе данных. Сохранение изменений в базе данных может быть выполнено при помощи метода SaveChanges.

Следующий пример получает список заказчиков из Лондона и устанавливает значение свойства Country для каждого из них в «United Kingdom». Изменения сохраняются в StateManager, но не заносятся в базу данных, пока не вызывается метод SaveChanges:

var query = from c in northwindContext.Customers
  where c.City == “London”
  select c;
foreach (Customers c in query) c.Country = “United Kingdom”;
northwindContext.SaveChanges();

Вы также можете создать новый экземпляр сущности и добавить его в EDM, используя метод AddObject объекта ObjectContext. В следующем примере показано, как добавить новую категорию в таблицу базы данных Categories:

Categories newCat = new Categories();
newCat.CategoryName = “Other”;
northwindContext.AddObject(newCat);
northwindContext.SaveChanges();
int newCatID = newCat.CategoryID; 

Сперва создается экземпляр сущности Categories и устанавливается значение его свойства CategoryName. Затем новая категория добавляется в EDM вызовом метода AddObject. Когда вызывается метод SaveChanges, создается оператор SQL, который сохраняет новую Category в базе данных и возвращает идентификатор CategoryID для новой строки.

Когда между сущностями есть связи, вам может потребоваться ассоциировать новую сущность с уже существующей. Например, можно создать новую сущность Orders и связать ее свойство Customers с существующей сущностью Customers. Хотя в таблице Orders в базе данных есть поле CustomerID, EDM представляет эту связь в объектно-ориентированном стиле, используя свойство Customers для обращения к существующей сущности Customers:

Orders newOrder = new Orders();
newOrder.OrderDate = DateTime.Today;
Customers cust = northwindContext.Customers.Where(
  “it.CustomerID = ‘ALFKI’”).First();
newOrder.Customers = cust;
northwindContext.AddObject(newOrder);
northwindContext.SaveChanges();

Заключение

Entity Framework позволяет разработчикам обращаться к данным, используя объектную модель вместо логической или реляционной модели данных. Разработав EDM и сопоставление реляционному хранилищу, можно взаимодействовать с объектами различными способами: EntityClient, ObjectServices и LINQ.

Хотя традиционные объекты, такие как DataSet, DataAdapter, DbConnection и DbCommand по-прежнему поддерживаются в следующем выпуске ADO.NET, доступном в Visual Studio «Orcas», Entity Framework привносит значительные дополнения, открывающие для ADO.NET новые удивительные возможности.

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