Вернемся к создаваемому демонстрационному проекту каталога книг. Давайте посмотрим, каким образом в нем используется рассматриваемая библиотека Entity Framework.
Давайте вспомним, что на данный момент веб-приложение уже вполне работоспособно. При помощи заготовок были созданы Контроллеры и Представления необходимые для работы с указанными классами Модели. Кроме того, был добавлен код, использующий Entity Framework для взаимодействия с базой данных. Разумеется возникает вопрос: а где она сама?
Наверное многие уже догадались, что в данном случае используется подход Код вначале (Code First). И именно благодаря ему упрощается разработка веб-приложения и автоматически создается база данных со всеми необходимыми таблицами. В этой и следующей части рассмотрим его подробнее.
Необходимые составляющие
Что необходимо для работы Entity Framework Code First, разумеется без учета самого ядра библиотеки? Это контекст базы данных, строка соединения и исходный код классов Модели.
Классы Модели
Здесь стоит обратить внимание на тот факт, что в исходном коде классов Модели на данный момент нет никаких атрибутов. Равно как и на факт отсутствия необходимости наследования их от специальных базовых классов. Это обычные POCO (Plain Old CLR Object) классы.
Дело в том, что Сode First для генерация базы данных использует ряд соглашений по умолчанию. Они определяют правила создания таблиц и достаточны для достаточно большого числа ситуаций. Разумеется, при необходимости их можно уточнить, используя атрибуты или с помощью специального интерфейса Fluent API. Все это будет рассмотрено далее. А пока просто еще раз выделим тот факт, что для работы Code First достаточно простых классов, которые на данный момент и присутствуют в создаваемом веб-приложении.
Контекст базы данных
Следующая составляющая – контекст базы данных, который расположен в классе CatalogContext. Вот его исходный код:
namespace BookCatalog.Models.DbContext
{
using System.Data.Entity;
using BookCatalog.Models;
public class CatalogContext : DbContext
{
// You can add custom code to this file. Changes will not be overwritten.
//
// If you want Entity Framework to drop and regenerate your database
// automatically whenever you change your model schema, add the following
// code to the Application_Start method in your Global.asax file.
// Note: this will destroy and re-create your database with every model change.
//
// System.Data.Entity.Database.SetInitializer(new System.Data.Entity.DropCreateDatabaseIfModelChanges<BookCatalog.Models.CatalogContext>());
public DbSet<Tag> Tags { get; set; }
public DbSet<Publisher> Publishers { get; set; }
public DbSet<Language> Languages { get; set; }
public DbSet<BookDetails> Books { get; set; }
}
}
Он содержит свойства с помощью которых можно обмениваться информацией с базой данных. Все они имеют тип DbSet<T> и могут быть использованы для получения экземпляров T. При необходимости можно расширить этот список и указать другие классы Модели, добавив свойства вида:
public DbSet<MyClass> MyTableName { get; set; }
Код этого класса можно модифицировать самостоятельно. В дальнейшем, при необходимости помощники ASP.NET MVC 3 будут добавлять в него новые свойства, не изменяя остальное содержимое.
Строка соединения
Строка соединения определяет с какой системой управления базами данных будет взаимодействовать ядро Code First. Обратите внимание, что её имя в web.config совпадает с именем класса контекста, а вид гораздо проще, чем у обычной строки Entity Framework:
.........
</appSettings>
<connectionStrings>
<add name="CatalogContext"
connectionString="Data source=|DataDirectory|\BooksCatalog.sdf"
providerName="System.Data.SqlServerCe.4.0" />
</connectionStrings>
<system.web>
.........
Приведенный выше вариант демонстрирует настройку подключения к Microsoft SQL Server Compact. Файл базы данных будет создан в папке App_Data и получит имя BooksCatalog.sdf.
При желании можно воспользоваться Microsoft SQL Server Express или полной версией Microsoft SQL Server. Тогда строка соединения будет примерно вот такой:
.........
</appSettings>
<connectionStrings>
<add name="CatalogContext"
connectionString="Data Source=(local); Initial Catalog=BookCatalog; Integrated Security=true;"
providerName="System.Data.SqlClient" />
</connectionStrings>
<system.web>
.........
Обратите внимание, что в данном случае тип используемой системы управления базами данных никак не влияет на исходный код. Все настройки делаются в файле конфигурации web.config (или в app.config). Разумеется, при необходимости их можно задать и программно.
Таким образом можно сказать, что классы Модели, контекст и строка соединения – это все что необходимо для организации работы с базой данных в рамках подхода Code First.
Созданные таблицы
Давайте теперь посмотрим на полученную в результате базу данных. Для этого потребуются SQL CE Tools for Visual Studio SP1. В Solution Navigator или Solution Explorer откроем папку App_Data. В ней сделаем двойной щелчок мышкой на файле BooksCatalog.sdf. Если его не видно, то необходимо в её панели включить отображение всех файлов.
После первого запуска веб-приложения Entity Framework создал следующие таблицы:
- Основные, для хранения данных. В их основе лежат указанные в CatalogContext классы. При этом используются соглашения, которые будут рассмотрены ниже.
- Вспомогательные, для организации связей (в данном случае это BookDetailsTags, обеспечивающая взаимосвязь между BookDetails и Tags).
- Служебную EdmMetadata. В ней содержится хэш-код, с помощью которого ядро Entity Framework определяет был ли изменен исходный код классов Модели. В этом случае потребуется пересоздать базу данных.
Давайте рассмотрим принципы соглашений, на базе которых и создаются таблицы.
Соглашения Entity Framework Code First
Навигационные свойства
Свойства, которые не представляют непосредственно конкретное поле таблицы, а используются для доступа к данным, связанным с выбранной записью, называются навигационными. Посмотрим на примере:
namespace BookCatalog.Models
{
using System.Collections.Generic;
public class Tag
{
public int Id { get; set; }
public string Text { get; set; }
public virtual ICollection<BookDetails> Books { get; set; }
}
}
В данном случае таким свойством является Books. Оно представляет коллекцию книг, связанных с определенным ключевым словом. При обращении к нему данные будут взяты из таблицы BookDetails.
Имена таблиц
Для создания имен таблиц Entity Framework Code First по умолчанию использует имена соответствующих классов. Это поведение может быть переопределено атрибутами или с использованием Fluent API.
Первичный ключ таблицы
Свойства с именем Id или <имя класса>Id считается первичным ключом создаваемой таблицы. При этом регистр символов не учитывается.
Внешние ключи таблицы
Внешние ключи таблицы задаются с использованием пары свойств: обычного и навигационного. При этом первое должно иметь один из двух вариантов имени:
- <имя навигационного свойства><имя первичного ключа из связанной таблицы>;
- <имя класса, представляющего связанную таблицу><имя её первичного ключа>.
При этом обязательность такой связи напрямую зависит от того, может ли обычное свойство принимать значение null. Например:
namespace BookCatalog.Models
{
using System;
using System.Collections.Generic;
public class BookDetails
{
.........
public int LanguageId { get; set; }
public int? PublisherId { get; set; }
.........
public virtual Language Language { get; set; }
public virtual Publisher Publisher { get; set; }
}
}
В результате Entity Framework Code First создаст два внешних ключа:
- LanguageId, который должен быть обязательно связан с полем Id таблицы Languages;
- PublisherId, который может быть связан с полем Id таблицы Publishers.
Обнаружение используемых типов
Entity Framework Code First определяет используемые типы и автоматически включает их в создаваемую Концептуальную модель. Например, в CatalogContext можно указать только свойство BookDetails. Но при этом все равно будут созданы остальные таблицы, т.к. связанные с ними типы используется в Модели. А вот обратиться к ним будет уже не возможно, поскольку нет соответствующих свойств. Разумеется, ничего не мешает добавить их позже.
Указание отношений
Code First позволяет определить различные типы отношений между строками двух таблиц.
- One-to-one (одна к одной): данный тип отношений рассмотрен ранее, при описании внешних ключей.
- One-to-many (одна к многим): в одном классе указывается внешний ключ, а в другом связанная с ним коллекция типа ICollection<T>:
public class BookDetails
{
.........
public int? PublisherId { get; set; }
.........
public virtual Publisher Publisher { get; set; }
}
public class Publisher
{
public int Id { get; set; }
.........
public virtual ICollection<BookDetails> Books { get; set; }
}
- Many-to-many (многие к многим): оба класса содержат коллекции ICollection<T> друг друга:
public class BookDetails
{
.........
public virtual ICollection<Tag> Tags { get; set; }
.........
}
.........
public class Tag
{
.........
public virtual ICollection<BookDetails> Books { get; set; }
}
В следующей части рассмотрим каким образом можно установить соотношения таблиц и классов Модели. Причем даже в том случае, если изменить исходный код последних нельзя.