API Fluent — связи

Примечание.

Эта страница содержит сведения о настройке связей в модели Code First с помощью свободного API. Общие сведения о отношениях в EF и о том, как получить доступ к данным и управлять ими с помощью связей, см. в разделе "Связи" и "Свойства навигации".

При работе с кодом First вы определяете модель, определяя классы CLR домена. По умолчанию Entity Framework использует соглашения Code First для сопоставления классов с схемой базы данных. Если вы используете соглашения об именовании code First, в большинстве случаев можно использовать Code First для настройки связей между таблицами на основе внешних ключей и свойств навигации, которые определяются в классах. Если вы не следуете соглашениям при определении классов или хотите изменить способ работы соглашений, вы можете использовать простой API или заметки данных для настройки классов, чтобы Code First могли сопоставить связи между таблицами.

Введение

При настройке связи с api fluent вы начинаете с экземпляра EntityTypeConfiguration, а затем используете метод HasRequired, HasOptional или HasMany, чтобы указать тип связи, в который участвует эта сущность. Методы HasRequired и HasOptional принимают лямбда-выражение, представляющее свойство ссылочной навигации. Метод HasMany принимает лямбда-выражение, представляющее свойство навигации коллекции. Затем можно настроить свойство обратной навигации с помощью методов WithRequired, WithOptional и WithMany. Эти методы имеют перегрузки, которые не принимают аргументы и могут использоваться для указания карта inality с однонаправленными навигациями.

Затем можно настроить свойства внешнего ключа с помощью метода HasForeignKey. Этот метод принимает лямбда-выражение, представляющее свойство, которое будет использоваться в качестве внешнего ключа.

Настройка связи "обязательный к необязательным" (один к нулю или одному)

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

// Configure the primary key for the OfficeAssignment
modelBuilder.Entity<OfficeAssignment>()
    .HasKey(t => t.InstructorID);

// Map one-to-zero or one relationship
modelBuilder.Entity<OfficeAssignment>()
    .HasRequired(t => t.Instructor)
    .WithOptional(t => t.OfficeAssignment);

Настройка связи, в которой требуются оба конца (один к одному)

В большинстве случаев Entity Framework может определить, какой тип является зависимым и который является субъектом в связи. Однако, если оба конца связи являются обязательными или обе стороны являются необязательными Entity Framework, не могут определить зависимый и субъект. Если оба конца связи обязательны, используйте WithRequiredPrincipal или WithRequiredDependent после метода HasRequired. Если оба конца связи являются необязательными, используйте WithOptionalPrincipal или WithOptionalDependent после метода HasOptional.

// Configure the primary key for the OfficeAssignment
modelBuilder.Entity<OfficeAssignment>()
    .HasKey(t => t.InstructorID);

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

Настройка связи "многие ко многим"

Следующий код настраивает связь "многие ко многим" между типами Course и Instructor. В следующем примере для создания таблицы соединения используются соглашения Code First по умолчанию. В результате таблица CourseInstructor создается с Course_CourseID и столбцами Instructor_InstructorID.

modelBuilder.Entity<Course>()
    .HasMany(t => t.Instructors)
    .WithMany(t => t.Courses)

Если вы хотите указать имя таблицы соединения и имена столбцов в таблице, необходимо выполнить дополнительную настройку с помощью метода Map. Следующий код создает таблицу CourseInstructor с столбцами CourseID и InstructorID.

modelBuilder.Entity<Course>()
    .HasMany(t => t.Instructors)
    .WithMany(t => t.Courses)
    .Map(m =>
    {
        m.ToTable("CourseInstructor");
        m.MapLeftKey("CourseID");
        m.MapRightKey("InstructorID");
    });

Настройка связи с одним свойством навигации

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

// Configure the primary Key for the OfficeAssignment
modelBuilder.Entity<OfficeAssignment>()
    .HasKey(t => t.InstructorID);

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

Включение каскадного удаления

Каскадное удаление можно настроить с помощью метода WillCascadeOnDelete. Если внешний ключ для зависимой сущности не имеет значения NULL, код сначала задает каскадное удаление связи. Если внешний ключ зависимой сущности имеет значение NULL, code First не устанавливает каскадное удаление связи, а при удалении внешнего ключа субъект будет иметь значение NULL.

С помощью следующих соглашений об каскадных удалениях можно удалить следующее:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>()
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>()

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

modelBuilder.Entity<Course>()
    .HasRequired(t => t.Department)
    .WithMany(t => t.Courses)
    .HasForeignKey(d => d.DepartmentID)
    .WillCascadeOnDelete(false);

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

Если первичный ключ в типе Отдела состоит из свойств DepartmentID и Name, необходимо настроить первичный ключ для Отдела и внешний ключ для типов курсов следующим образом:

// Composite primary key
modelBuilder.Entity<Department>()
.HasKey(d => new { d.DepartmentID, d.Name });

// Composite foreign key
modelBuilder.Entity<Course>()  
    .HasRequired(c => c.Department)  
    .WithMany(d => d.Courses)
    .HasForeignKey(d => new { d.DepartmentID, d.DepartmentName });

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

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

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

Настройка имени внешнего ключа, которое не соответствует первому соглашению о коде

Если свойство внешнего ключа класса Course было названо SomeDepartmentID вместо DepartmentID, необходимо сделать следующее, чтобы указать, что необходимо, чтобы SomeDepartmentID был внешним ключом:

modelBuilder.Entity<Course>()
         .HasRequired(c => c.Department)
         .WithMany(d => d.Courses)
         .HasForeignKey(c => c.SomeDepartmentID);

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

Следующая модель 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; }
}