Казахстан, Алматы, Гимназия им. Ахмета Байтурсынова №139, 2008 |
Проектирование баз данных и работа с ними Веб-приложений. LINQ, ADO.NET Entities, DDD
10.1.3.4. Сопоставление хранилищу
Файл 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. Пример файла 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>
10.1.3.5. Определение наследования
EDM также поддерживает модели, не соответствующие базе данных взаимно-однозначно. Например, используя базу данных Northwind, можно создать класс, например, называющийся DiscontinuedProducts, который наследует все свойства класса Products, но содержит только продукты, значение поля Discontinued для которых равно 1. Это упрощенная схема наследования, но она показывает, как применить наследование в EDM.
Первым шагом по созданию класса DiscontinuedProducts в концептуальной модели является открытие файла CSDL, создание нового типа EntityType, называющегося DiscontinuedProducts, и установка его атрибута BaseType в значение NorthwindModel.Products (схема и название базового EntityType ). Порожденный тип EntityType наследует свойства 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, в котором происходит сопоставление унаследованной сущности:
<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>
10.1.3.6. Сопоставление нескольким таблицам
Другим способом уйти в EDM от строгого взаимно-однозначного сопоставления модели хранилищу является сопоставление одной сущности в модели нескольким таблицам в хранилище. Между таблицами Contacts и ContactNameSplit в базе данных Northwind есть взаимно-однозначная связь, и можно объединить их в одну сущность в модели. Для примера создадим в модели сущность, включающую все поля таблицы Contacts и поля Title и Name из таблицы ContactNameSplit.
Первым изменением является добавление двух дополнительных свойств в элементе EntityType Contacts в файле CSDL: Name и Title. Эти два новых свойства в модели теперь должны быть сопоставлены хранилищу в файле MSL. Необходимо изменить элемент EntitySetMapping для EntitySet Contacts, чтобы представлять сопоставление нескольким таблицам, например, удалив атрибуты TableName и TypeName. Эти атрибуты объявляются в элементе EntitySetMapping, только если набор EntitySet в модели взаимно-однозначно сопоставлен набору EntitySet в хранилище.
Поскольку сопоставление набора EntitySet модели набору EntitySet хранилища для Contacts было удалено, необходимо создать ему замену. Такой заменой является дочерний элемент EntityTypeMapping. Их необходимо создать два – по одному для представления каждой из таблиц Contacts и ContactNameSplit в хранилище. Элементы EntityTypeMapping определяют атрибут TypeName для каждого из наборов EntitySet.
Внутри каждого из элементов EntityTypeMapping помещен дочерний элемент, называющийся TableMappingFragment. Этот элемент включает атрибут TableName, соответствующий набору EntitySet в хранилище. В TableMappingFragment определены все элементы ScalarProperty, сопоставляющие свойства модели хранилищу. Далее показан обновленный элемент EntitySetMapping Contacts, который теперь сопоставляет таблицы Contacts и ContactSplitName хранилища одному набору EntitySet модели:
<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:EntitySetMapping> <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>
10.1.3.7. Использование EntityClient
Доступ к концептуальной модели Entity Framework может быть организован тремя способами: EntityClient, Object Services, LINQ to Entities
Рассмотрим первый их них.
EntityClient абстрагирован от логического хранилища, поскольку он взаимодействует с концептуальной моделью посредством своего собственного текстового языка, называющегося Entity SQL. Все запросы Entity SQL, выполняемые через EntityClient, компилируются в деревья команд, посылаемые в хранилище. Преобразование запросов Entity SQL через концептуальную модель и далее в хранилище обеспечивается Entity Framework.
Классы EntityClient похожи на классы распространенных поставщиков ADO.NET. Например, запросы EntityClient выполняются в объекте EntityCommand, которому необходим объект EntityConnection для подключения к EDM. Хотя EntityClient взаимодействует с сущностями EDM, он не возвращает экземпляры сущностей, а вместо этого возвращает все результаты в виде объекта DbDataReader. При помощи DbDataReader EntityClient может возвращать стандартный набор строк и столбцов, либо представление более сложной иерархии данных.
На следующем примере показано использование EntityClient для подключения к концептуальной модели и получения списка заказчиков из Москвы:
string city = "Москва"; 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(); }
EntityConnection может воспринимать полную строку подключения к концептуальному слою или ее название в файле App.Config. Строка подключения содержит перечень файлов метаданных (файлов CSDL, MSL и SSDL), а также строку подключения к хранилищу, зависящую от конкретной базы данных:
"metadata=.\NorthwindEntities.csdl|.\NorthwindEntities.ssdl|.\NorthwindEntities.msl;provider=System.Data.SqlClient; provider connection string=' Data Source=.\SQLEXPRESS;Initial Catalog=Northwind; Integrated Security=True; User Instance=True'"
Код в примере показывает, как создать объект EntityConnection и выполнить для него команду EntityCommand. Запрос, написанный на Entity SQL, обращается к набору EntitySet Customers в EDM.
10.1.3.8. Использование служб Object Services
Другим способом взаимодействия с данными, представленными EDM, является использование служб Object Services. Службы Object Services предоставляют возможность загружать объекты и следовать любым связям, определенным в EDM. Службы 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 = "Москва"; 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.