Настройка схемы и таблиц базы данных
194Работа с базами данных в .NET Framework --- Entity Framework 6 --- Настройка схемы базы данных
К этому моменту вы уже познакомились с соглашениями 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);
}