Fluent API — relacje

Uwaga

Ta strona zawiera informacje o konfigurowaniu relacji w modelu Code First przy użyciu płynnego interfejsu API. Aby uzyskać ogólne informacje o relacjach w programie EF oraz sposobie uzyskiwania dostępu do danych i manipulowania nimi przy użyciu relacji, zobacz Relacje i właściwości nawigacji.

Podczas pracy z usługą Code First zdefiniujesz model, definiując klasy CLR domeny. Domyślnie program Entity Framework używa konwencji Code First do mapowania klas na schemat bazy danych. Jeśli używasz konwencji nazewnictwa Code First, w większości przypadków można polegać na korzystaniu z funkcji Code First, aby skonfigurować relacje między tabelami na podstawie kluczy obcych i właściwości nawigacji zdefiniowanych w klasach. Jeśli nie stosujesz konwencji podczas definiowania klas lub chcesz zmienić sposób działania konwencji, możesz użyć płynnego interfejsu API lub adnotacji danych, aby skonfigurować klasy, aby program Code First mógł mapować relacje między tabelami.

Wprowadzenie

Podczas konfigurowania relacji z płynnym interfejsem API rozpoczynasz od wystąpienia EntityTypeConfiguration, a następnie użyj metody HasRequired, HasOptional lub HasMany, aby określić typ relacji, w której uczestniczy ta jednostka. Metody HasRequired i HasOptional przyjmują wyrażenie lambda reprezentujące właściwość nawigacji referencyjnej. Metoda HasMany przyjmuje wyrażenie lambda reprezentujące właściwość nawigacji kolekcji. Następnie można skonfigurować właściwość nawigacji odwrotnej przy użyciu metod WithRequired, WithOptional i WithMany. Te metody mają przeciążenia, które nie przyjmują argumentów i mogą służyć do określania kardynalności z nawigacjami jednokierunkowymi.

Następnie można skonfigurować właściwości klucza obcego przy użyciu metody HasForeignKey. Ta metoda przyjmuje wyrażenie lambda, które reprezentuje właściwość, która ma być używana jako klucz obcy.

Konfigurowanie relacji wymaganej do opcjonalnej (jeden do zera lub jednego)

Poniższy przykład konfiguruje relację jeden-do-zero-lub-jeden. Właściwość OfficeAssignment ma właściwość InstructorID, która jest kluczem podstawowym i kluczem obcym, ponieważ nazwa właściwości nie jest przestrzegana zgodnie z konwencją metoda HasKey jest używana do konfigurowania klucza podstawowego.

// 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);

Konfigurowanie relacji, w której wymagane są oba końce (jeden do jednego)

W większości przypadków program Entity Framework może wywnioskować, który typ jest zależny i który jest podmiotem zabezpieczeń w relacji. Jednak gdy oba końce relacji są wymagane lub obie strony są opcjonalne Entity Framework nie mogą zidentyfikować zależności i podmiotu zabezpieczeń. Gdy oba końce relacji są wymagane, użyj metody WithRequiredPrincipal lub WithRequiredDependent po metodzie HasRequired. Gdy oba końce relacji są opcjonalne, użyj metody WithOptionalPrincipal lub WithOptionalDependent po metodzie 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);

Konfigurowanie relacji wiele-do-wielu

Poniższy kod konfiguruje relację wiele-do-wielu między typami Kurs i Instruktor. W poniższym przykładzie domyślne konwencje Code First są używane do tworzenia tabeli sprzężenia. W rezultacie tabela CourseInstructor jest tworzona z kolumnami Course_CourseID i Instructor_InstructorID.

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

Jeśli chcesz określić nazwę tabeli sprzężenia i nazwy kolumn w tabeli, musisz wykonać dodatkową konfigurację przy użyciu metody Map. Poniższy kod generuje tabelę CourseInstructor z kolumnami CourseID i InstructorID.

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

Konfigurowanie relacji z jedną właściwością nawigacji

Relacja jednokierunkowa (nazywana również jednokierunkową) polega na tym, że właściwość nawigacji jest definiowana tylko na jednym z końców relacji, a nie na obu. Zgodnie z konwencją Code First zawsze interpretuje relację jednokierunkową jako jeden do wielu. Jeśli na przykład chcesz mieć relację jeden do jednego między instruktorem i pakietem OfficeAssignment, gdzie masz właściwość nawigacji tylko dla typu instruktora, musisz użyć płynnego interfejsu API, aby skonfigurować tę relację.

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

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

Włączanie usuwania kaskadowego

Usuwanie kaskadowe można skonfigurować w relacji przy użyciu metody WillCascadeOnDelete. Jeśli klucz obcy jednostki zależnej nie jest dopuszczalny do wartości null, funkcja Code First ustawia kaskadowe usunięcie relacji. Jeśli klucz obcy jednostki zależnej ma wartość null, funkcja Code First nie ustawia kaskadowego usuwania relacji, a gdy podmiot zabezpieczeń zostanie usunięty, klucz obcy zostanie ustawiony na wartość null.

Te kaskadowe konwencje usuwania można usunąć przy użyciu:

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

Poniższy kod konfiguruje wymaganą relację, a następnie wyłącza usuwanie kaskadowe.

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

Konfigurowanie złożonego klucza obcego

Jeśli klucz podstawowy w typie Działu składa się z właściwości DepartmentID (Identyfikator działu) i Name (Nazwa), należy skonfigurować klucz podstawowy dla działu i klucza obcego w typach kursu w następujący sposób:

// 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 });

Zmiana nazwy klucza obcego, który nie jest zdefiniowany w modelu

Jeśli zdecydujesz się nie definiować klucza obcego w typie CLR, ale chcesz określić, jaka nazwa powinna znajdować się w bazie danych, wykonaj następujące czynności:

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

Konfigurowanie nazwy klucza obcego, które nie jest zgodne z pierwszą konwencją kodu

Jeśli właściwość klucza obcego w klasie Course nosiła nazwę SomeDepartmentID zamiast DepartmentID, należy wykonać następujące czynności, aby określić, że element SomeDepartmentID ma być kluczem obcym:

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

Model używany w przykładach

Poniższy model Code First jest używany dla przykładów na tej stronie.

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; }
}