Настройка схемы и таблиц базы данных

194

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

Имена таблиц и схемы базы данных

По умолчанию, Entity Framework использует в качестве имен таблиц базы данных названия классов модели во множественном числе, используя правила английского языка. Например, класс Customer отображается на таблицу Customers, а класс Order на таблицу Orders в базе данных. С помощью аннотаций данных или Fluent API можно явно задавать имя генерируемой таблицы в базе данных.

Для явной установки имен таблиц в аннотациях используется атрибут Table, которому передается имя таблицы. Этот атрибут находится в пространстве имен System.ComponentModel.DataAnnotations.Schema, ниже показан пример его использования:

[Table("Customer")]
public class Customer
{
    // ...
}

[Table("Order")]
public class Order
{
    // ...
}

В результате запуска этого примера, Entity Framework сгенерирует имена таблиц, соответствующие именам, указанным в атрибуте Table. Также можно настроить имя схемы базы данных. Схема (schema) - это объект базы данных, содержащий инструкции для создания таблиц, представлений и пользовательских разрешений. Схему можно рассматривать, как конструкцию, в которой собраны вместе несколько таблиц, соответствующие представления и пользовательские разрешения.

В T-SQL применяется такое же понятие схемы, как и в стандарте ANSI SQL. В стандарте SQL схема определяется как коллекция объектов базы данных, имеющая одного владельца и формирующая одно пространство имен. Пространство имен (namespace) - это набор объектов с однозначными именами. Например, две таблицы могут иметь одно и то же имя только в том случае, если они находятся в разных схемах. Схема является очень важным концептом в модели безопасности компонента Database Engine СУБД SQL Server. Схемой по умолчанию, для всех объектов базы данных является схема dbo.

Для настройки имени схемы в аннотациях, также используется атрибут Table с двумя параметрами, как показано в примере ниже:

[Table("Customer", Schema="poco")]
public class Customer
{
    // ...
}

[Table("Order", Schema="poco")]
public class Order
{
    // ...
}

На рисунке ниже показана структура таблиц, сгенерированной базы данных MyShop для этого примера. Обратите внимание на имена таблиц с указанием схемы базы данных:

Указание схемы для таблиц базы данных

В Fluent API настройка имен таблиц и схемы осуществляется с помощью вспомогательного метода ToTable(), который вызывается на объекте сущности для нужной таблицы. Ниже показан пример использования этого метода, в котором осуществляются настройки аналогичные показанным выше:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Customer>().ToTable("Customer", "poco");
    modelBuilder.Entity<Order>().ToTable("Order", "poco");
}

Имена столбцов таблицы

Вы можете не только переопределять имена таблиц, вы также можете изменить имя столбца таблицы базы данных. По умолчанию, Code-First создает столбцы с именами, соответствующими именам свойств в классе модели. Это поведение является не всегда тем, что нужно. Например, мы можем поменять имя первичного ключа в таблице Customer на UserId. В аннотациях для этих целей используется атрибут Column, которому передается строка, которой нужно заменить имя свойства. В Fluent API для этого используется специальный метод HasColumnName(), который вызывается на уровне конфигурации свойства (после вызова метода Property()). Ниже показан пример использования этих настроек для свойства CustomerId:

public class Customer
{
    [Column("UserId")]
    public int CustomerId { get; set; }

    // ...
}


// то же самое с помощью Fluent API

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
     modelBuilder.Entity<Customer>().Property(c => c.CustomerId)
         .HasColumnName("UserId");
}

Стоит отметить, что явное изменение имени первичного ключа не влияет на соглашения Code-First по поиску этого ключа в классе модели. Code-First использует механизм рефлексии для извлечения имени свойства CustomerId и не обращает внимание на атрибут Column, в результате чего данный пример работает корректно – столбец UserId в таблице Customers будет являться первичным ключом этой таблицы.

Ранее, когда мы обсуждали возможность использования сложных типов на примере таблицы User и типа Address, мы говорили, что Entity Framework автоматически сгенерирует имена столбцов для сложных типов, наподобие Address_City или Address_ZipCode. В Code-First разрешается конфигурировать имена столбцов, создаваемых из сложных типов. Например:

public class User
{
        public int UserId { get; set; }
        public int SocialNumber { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public Address Address { get; set; }
}

[ComplexType]
public class Address
{
        public int AddressId { get; set; }

        [Column("StreetAddress")]
        public string StreetAddress { get; set; }

        [Column("City")]
        public string City { get; set; }

        [Column("ZipCode")]
        public string ZipCode { get; set; }
}

В результате Entity Framework сгенерирует понятные имена столбцов в таблице Users без использования префикса Address. Те же самые настройки для сложных типов с использованием Fluent API показаны ниже:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>()
        .Property(u => u.Address.StreetAddress)
        .HasColumnName("StreetAddress");

    modelBuilder.Entity<User>()
        .Property(u => u.Address.City)
        .HasColumnName("City");

    modelBuilder.Entity<User>()
        .Property(u => u.Address.ZipCode)
        .HasColumnName("ZipCode");
}

Атрибут Column или соответствующие настройки Fluent API также используются для настройки составных первичных ключей для таблицы (как вы наверное знаете, в таблицах может содержаться более одного ключа). В предыдущей статье мы рассмотрели пример создания связи многие-ко-многим между таблицами Customers и Products через промежуточную таблицу Orders. В нашем примере таблица Orders генерировалась автоматически и содержала два внешних ключа, указывающих соответственно на таблицы Customers и Products. При таком раскладе мы не сохраняли информацию, в какой момент пользователь делает заказ и какое количество товара он заказывает.

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

public class Customer
{
    public int CustomerId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
    public byte[] Photo { get; set; }

    // Ссылка на заказы
    public virtual List<Order> Orders { get; set; }
}

public class Order
{
    // Два первичных/внешних ключа в промежуточной таблице Orders
    [Key, ForeignKey("Customer")]
    public int CustomerId { get; set; }

    [Key, ForeignKey("Product")]
    public int ProductId { get; set; }

    public int Quantity { get; set; }
    public DateTime PurchaseDate { get; set; }

    // Ссылка на покупателя
    public Customer Customer { get; set; }

    // Ссылка на товар
    public Product Product { get; set; }
}

public class Product
{
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public string Description { get; set; }
    public decimal Cost { get; set; }

    // Список заказов, содержащих этот товар
    public List<Order> Orders { get; set; }
}

После запуска приложения с такой моделью будет выдано исключение, т.к. Code-First не сможет определить порядок сортировки для двух или более первичных ключей (это является соглашением во многих базах данных – если вы определяете несколько первичных ключей, то должны явно указать порядок сортировки с использованием инструкции ORDER BY). Чтобы решить эту проблему, можно воспользоваться атрибутом Column, передав параметру Order значения от 0, указывающие на порядок сортировки:

public class Order
{
    // Два первичных/внешних ключа в промежуточной таблице Orders
    [Key, ForeignKey("Customer")]
    [Column("UserId", Order=0)]
    public int CustomerId { get; set; }

    [Key, ForeignKey("Product")]
    [Column(Order=1)]
    public int ProductId { get; set; }

    // ...
}


// то же самое с помощью Fluent API

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Order>().Property(o => o.CustomerId)
        .HasColumnOrder(0).HasColumnName("UserId");

    modelBuilder.Entity<Order>().Property(o => o.ProductId)
        .HasColumnOrder(1);
}
Пройди тесты
Лучший чат для C# программистов