Entity Framework. Часть 3 – Code First и соглашения

Вернемся к создаваемому демонстрационному проекту каталога книг. Давайте посмотрим, каким образом в нем используется рассматриваемая библиотека 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. При необходимости можно расширить этот список и указать другие классы Модели, добавив свойства вида:

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 создаст два внешних ключа:

  1. LanguageId, который должен быть обязательно связан с полем Id таблицы Languages;
  2. PublisherId, который может быть связан с полем Id таблицы Publishers.

Обнаружение используемых типов

Entity Framework Code First определяет используемые типы и автоматически включает их в создаваемую Концептуальную модель. Например, в CatalogContext можно указать только свойство BookDetails. Но при этом все равно будут созданы остальные таблицы, т.к. связанные с ними типы используется в Модели. А вот обратиться к ним будет уже не возможно, поскольку нет соответствующих свойств. Разумеется, ничего не мешает добавить их позже.

Указание отношений

Code First позволяет определить различные типы отношений между строками двух таблиц.

  • One-to-one (одна к одной): данный тип отношений рассмотрен ранее, при описании внешних ключей.
  • One-to-many (одна к многим): в одном классе указывается внешний ключ, а в другом связанная с ним коллекция типа ICollection:
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 друг друга:
public class BookDetails
{
    .........

    public virtual ICollection<Tag> Tags { get; set; }

    .........
}

.........
    
public class Tag
{
    .........

    public virtual ICollection<BookDetails> Books { get; set; }
}

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