API Fluent — настройка и сопоставление свойств и типов

При работе с Entity Framework Code First поведение по умолчанию заключается в сопоставлении классов POCO с таблицами с помощью набора соглашений, запеченных в EF. Однако иногда вы не можете или не хотите следовать этим соглашениям и должны сопоставлять сущности с чем-то, кроме того, что определяют соглашения.

Существует два основных способа настройки EF для использования чего-либо, отличного от соглашений, а именно примечаний или API простого API EFs. Заметки охватывают только подмножество функциональных возможностей api fluent, поэтому существуют сценарии сопоставления, которые невозможно достичь с помощью заметок. В этой статье показано, как использовать api fluent для настройки свойств.

Код первый простой API чаще всего обращается путем переопределения метода OnModelCreating в производном DbContext. В следующих примерах показано, как выполнять различные задачи с api fluent и позволяет скопировать код и настроить его в соответствии с моделью, если вы хотите увидеть модель, которую можно использовать с as-is, то она предоставляется в конце этой статьи.

Параметры на уровне модели

Схема по умолчанию (EF6 и далее)

Начиная с EF6 можно использовать метод HasDefaultSchema в DbModelBuilder, чтобы указать схему базы данных для всех таблиц, хранимых процедур и т. д. Этот параметр по умолчанию переопределяется для всех объектов, для которые явно настраивается другая схема.

modelBuilder.HasDefaultSchema("sales");

Пользовательские соглашения (EF6)

Начиная с EF6 вы можете создать собственные соглашения, чтобы дополнить те, которые включены в Code First. Дополнительные сведения см . в разделе "Первые соглашения о пользовательском коде".

Сопоставление свойств

Метод Property используется для настройки атрибутов для каждого свойства, относящегося к сущности или сложному типу. Метод Property используется для получения объекта конфигурации для заданного свойства. Параметры объекта конфигурации зависят от типа, настроенного; IsUnicode доступен только в строковых свойствах, например.

Настройка первичного ключа

Соглашение Entity Framework для первичных ключей:

  1. Класс определяет свойство, имя которого — "ID" или "Id"
  2. или имя класса, за которым следует "ID" или "Id"

Чтобы явно задать свойство для первичного ключа, можно использовать метод HasKey. В следующем примере метод HasKey используется для настройки первичного ключа InstructorID в типе OfficeAssignment.

modelBuilder.Entity<OfficeAssignment>().HasKey(t => t.InstructorID);

Настройка составного первичного ключа

В следующем примере свойства DepartmentID и Name настраивают в качестве составного первичного ключа типа Department.

modelBuilder.Entity<Department>().HasKey(t => new { t.DepartmentID, t.Name });

Отключение удостоверения для числовых первичных ключей

В следующем примере свойство DepartmentID задает значение System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None, чтобы указать, что значение не будет создано базой данных.

modelBuilder.Entity<Department>().Property(t => t.DepartmentID)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

Указание максимальной длины для свойства

В следующем примере свойство Name должно быть не более 50 символов. Если значение больше 50 символов, вы получите исключение DbEntityValidationException . Если code First создает базу данных из этой модели, она также устанавливает максимальную длину столбца Name в 50 символов.

modelBuilder.Entity<Department>().Property(t => t.Name).HasMaxLength(50);

Настройка необходимого свойства

В следующем примере требуется свойство Name. Если имя не указано, вы получите исключение DbEntityValidationException. Если Code First создает базу данных из этой модели, столбец, используемый для хранения этого свойства, обычно не допускает значение NULL.

Примечание.

В некоторых случаях столбец в базе данных может быть не допускаемым значением NULL, даже если это свойство является обязательным. Например, при использовании данных стратегии наследования TPH для нескольких типов хранятся в одной таблице. Если производный тип содержит обязательное свойство, столбец нельзя сделать не допускаемым значение NULL, так как не все типы в иерархии будут иметь это свойство.

modelBuilder.Entity<Department>().Property(t => t.Name).IsRequired();

Настройка индекса для одного или нескольких свойств

Примечание.

ТОЛЬКО EF6.1 — атрибут индекса появился в Entity Framework 6.1. Если вы используете более раннюю версию, сведения в этом разделе не применяются.

Создание индексов изначально не поддерживается API Fluent, но вы можете использовать поддержку IndexAttribute через API Fluent. Атрибуты индекса обрабатываются путем включения заметки модели в модель, которая затем превращается в индекс в базе данных позже в конвейере. Вы можете вручную добавить эти же заметки с помощью API Fluent.

Самый простой способ сделать это — создать экземпляр IndexAttribute , содержащий все параметры нового индекса. Затем можно создать экземпляр IndexAnnotation , который является определенным типом EF, который преобразует параметры IndexAttribute в заметку модели, которая может храниться в модели EF. Затем их можно передать в метод HasColumnAnnotation в API Fluent, указав индекс имени заметки.

modelBuilder
    .Entity<Department>()
    .Property(t => t.Name)
    .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute()));

Полный список параметров, доступных в IndexAttribute, см. в разделе "Индекс" заметок для первых данных кода. Это включает настройку имени индекса, создание уникальных индексов и создание индексов с несколькими столбцами.

Можно указать несколько заметок индекса для одного свойства, передав массив IndexAttribute конструктору IndexAnnotation.

modelBuilder
    .Entity<Department>()
    .Property(t => t.Name)
    .HasColumnAnnotation(
        "Index",  
        new IndexAnnotation(new[]
            {
                new IndexAttribute("Index1"),
                new IndexAttribute("Index2") { IsUnique = true }
            })));

Указание не сопоставлять свойство CLR со столбцом в базе данных

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

modelBuilder.Entity<Department>().Ignore(t => t.Budget);

Сопоставление свойства CLR с определенным столбцом в базе данных

В следующем примере свойство Name CLR сопоставляется со столбцом базы данных DepartmentName.

modelBuilder.Entity<Department>()
    .Property(t => t.Name)
    .HasColumnName("DepartmentName");

Переименование внешнего ключа, который не определен в модели

Если вы решили не определить внешний ключ в типе СРЕДЫ CLR, но хотите указать имя, которое оно должно иметь в базе данных, сделайте следующее:

modelBuilder.Entity<Course>()
    .HasRequired(c => c.Department)
    .WithMany(t => t.Courses)
    .Map(m => m.MapKey("ChangedDepartmentID"));

Настройка того, поддерживает ли строковое свойство содержимое Юникода

По умолчанию строки — Юникод (nvarchar в SQL Server). Метод IsUnicode можно использовать для указания того, что строка должна быть типа varchar.

modelBuilder.Entity<Department>()
    .Property(t => t.Name)
    .IsUnicode(false);

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

Метод HasColumnType позволяет сопоставлять различные представления одного базового типа. Использование этого метода не позволяет выполнять преобразование данных во время выполнения. Обратите внимание, что IsUnicode является предпочтительным способом настройки столбцов в varchar, так как это не зависит от базы данных.

modelBuilder.Entity<Department>()   
    .Property(p => p.Name)   
    .HasColumnType("varchar");

Настройка свойств для сложного типа

Существует два способа настройки скалярных свойств для сложного типа.

Свойство можно вызвать в ComplexTypeConfiguration.

modelBuilder.ComplexType<Details>()
    .Property(t => t.Location)
    .HasMaxLength(20);

Вы также можете использовать нотацию точек для доступа к свойству сложного типа.

modelBuilder.Entity<OnsiteCourse>()
    .Property(t => t.Details.Location)
    .HasMaxLength(20);

Настройка свойства для использования в качестве маркера оптимистического параллелизма

Чтобы указать, что свойство в сущности представляет маркер параллелизма, можно использовать атрибут ConcurrencyCheck или метод IsConcurrencyToken.

modelBuilder.Entity<OfficeAssignment>()
    .Property(t => t.Timestamp)
    .IsConcurrencyToken();

Можно также использовать метод IsRowVersion для настройки свойства в виде версии строки в базе данных. Если свойство должно быть версией строки, автоматически настраивает его для оптимистического токена параллелизма.

modelBuilder.Entity<OfficeAssignment>()
    .Property(t => t.Timestamp)
    .IsRowVersion();

Сопоставление типов

Указание того, что класс является сложным типом

По соглашению тип, имеющий первичный ключ, не указанный, рассматривается как сложный тип. Существуют некоторые сценарии, когда Code First не обнаруживает сложный тип (например, если у вас есть свойство с именем ID, но это не означает, что он является первичным ключом). В таких случаях можно использовать api fluent, чтобы явно указать, что тип является сложным типом.

modelBuilder.ComplexType<Details>();

Указание не сопоставлять тип сущности CLR с таблицей в базе данных

В следующем примере показано, как исключить тип СРЕДЫ CLR из сопоставления с таблицей в базе данных.

modelBuilder.Ignore<OnlineCourse>();

Сопоставление типа сущности с определенной таблицей в базе данных

Все свойства Отдела будут сопоставлены со столбцами в таблице с именем t_ Отдел.

modelBuilder.Entity<Department>()  
    .ToTable("t_Department");

Можно также указать имя схемы следующим образом:

modelBuilder.Entity<Department>()  
    .ToTable("t_Department", "school");

Сопоставление наследования таблицы на уровне иерархии (TPH)

В сценарии сопоставления TPH все типы в иерархии наследования сопоставляются с одной таблицей. Для идентификации типа каждой строки используется дискриминационный столбец. При создании модели с кодом First TPH — это стратегия по умолчанию для типов, участвующих в иерархии наследования. По умолчанию столбец дискриминатора добавляется в таблицу с именем "Дискриминация" и именем типа CLR каждого типа в иерархии используется для дискриминационных значений. Поведение по умолчанию можно изменить с помощью api fluent.

modelBuilder.Entity<Course>()  
    .Map<Course>(m => m.Requires("Type").HasValue("Course"))  
    .Map<OnsiteCourse>(m => m.Requires("Type").HasValue("OnsiteCourse"));

Сопоставление наследования таблицы на тип (TPT)

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

modelBuilder.Entity<Course>().ToTable("Course");  
modelBuilder.Entity<OnsiteCourse>().ToTable("OnsiteCourse");

Сопоставление наследования табличного класса (TPC)

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

Вызовите метод MapInheritedProperties, чтобы настроить каждый производный тип. MapInheritedProperties переназначает все свойства, унаследованные от базового класса, к новым столбцам таблицы для производного класса.

Примечание.

Обратите внимание, что, так как таблицы, участвующие в иерархии наследования TPC, не используют первичный ключ, при вставке в таблицы, сопоставленные с подклассами, если у вас есть созданные значения с тем же начальным значением удостоверения. Чтобы решить эту проблему, можно указать другое начальное начальное значение для каждой таблицы или отключить удостоверение в свойстве первичного ключа. Удостоверение — это значение по умолчанию для свойств ключа целочисленного числа при работе с Code First.

modelBuilder.Entity<Course>()
    .Property(c => c.CourseID)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

modelBuilder.Entity<OnsiteCourse>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("OnsiteCourse");
});

modelBuilder.Entity<OnlineCourse>().Map(m =>
{
    m.MapInheritedProperties();
    m.ToTable("OnlineCourse");
});

Сопоставление свойств типа сущности с несколькими таблицами в базе данных (разделение сущностей)

Разделение сущностей позволяет распределять свойства типа сущности по нескольким таблицам. В следующем примере сущность Отдела разделена на две таблицы: Department и DepartmentDetails. Разделение сущностей использует несколько вызовов метода Map для сопоставления подмножества свойств с определенной таблицей.

modelBuilder.Entity<Department>()
    .Map(m =>
    {
        m.Properties(t => new { t.DepartmentID, t.Name });
        m.ToTable("Department");
    })
    .Map(m =>
    {
        m.Properties(t => new { t.DepartmentID, t.Administrator, t.StartDate, t.Budget });
        m.ToTable("DepartmentDetails");
    });

Сопоставление нескольких типов сущностей с одной таблицей в базе данных (разделение таблиц)

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

modelBuilder.Entity<OfficeAssignment>()
    .HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>()
    .HasRequired(t => t.OfficeAssignment)
    .WithRequiredPrincipal(t => t.Instructor);

modelBuilder.Entity<Instructor>().ToTable("Instructor");

modelBuilder.Entity<OfficeAssignment>().ToTable("Instructor");

Сопоставление типа сущности с вставками и обновлением и удалением хранимых процедур (EF6)

Начиная с EF6 можно сопоставить сущность с использованием хранимых процедур для вставки обновления и удаления. Дополнительные сведения см. в статье Code First Insert/Update/Delete Stored Procedures.

Модель, используемая в примерах

Следующая модель Code First используется для примеров на этой странице.

using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
// add a reference to System.ComponentModel.DataAnnotations DLL
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System;

public class SchoolEntities : DbContext
{
    public DbSet<Course> Courses { get; set; }
    public DbSet<Department> Departments { get; set; }
    public DbSet<Instructor> Instructors { get; set; }
    public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Configure Code First to ignore PluralizingTableName convention
        // If you keep this convention then the generated tables will have pluralized names.
        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
    }
}

public class Department
{
    public Department()
    {
        this.Courses = new HashSet<Course>();
    }
    // Primary key
    public int DepartmentID { get; set; }
    public string Name { get; set; }
    public decimal Budget { get; set; }
    public System.DateTime StartDate { get; set; }
    public int? Administrator { get; set; }

    // Navigation property
    public virtual ICollection<Course> Courses { get; private set; }
}

public class Course
{
    public Course()
    {
        this.Instructors = new HashSet<Instructor>();
    }
    // Primary key
    public int CourseID { get; set; }

    public string Title { get; set; }
    public int Credits { get; set; }

    // Foreign key
    public int DepartmentID { get; set; }

    // Navigation properties
    public virtual Department Department { get; set; }
    public virtual ICollection<Instructor> Instructors { get; private set; }
}

public partial class OnlineCourse : Course
{
    public string URL { get; set; }
}

public partial class OnsiteCourse : Course
{
    public OnsiteCourse()
    {
        Details = new Details();
    }

    public Details Details { get; set; }
}

public class Details
{
    public System.DateTime Time { get; set; }
    public string Location { get; set; }
    public string Days { get; set; }
}

public class Instructor
{
    public Instructor()
    {
        this.Courses = new List<Course>();
    }

    // Primary key
    public int InstructorID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public System.DateTime HireDate { get; set; }

    // Navigation properties
    public virtual ICollection<Course> Courses { get; private set; }
}

public class OfficeAssignment
{
    // Specifying InstructorID as a primary
    [Key()]
    public Int32 InstructorID { get; set; }

    public string Location { get; set; }

    // When Entity Framework sees Timestamp attribute
    // it configures ConcurrencyCheck and DatabaseGeneratedPattern=Computed.
    [Timestamp]
    public Byte[] Timestamp { get; set; }

    // Navigation property
    public virtual Instructor Instructor { get; set; }
}