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 i sposobie uzyskiwania dostępu do danych przy użyciu relacji i manipulowania nimi, zobacz Właściwości nawigacji relacji&.

Podczas pracy z programem 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 kodzie 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 należy rozpocząć od wystąpienia EntityTypeConfiguration, a następnie użyć metody HasRequired, HasOptional lub HasMany, aby określić typ relacji, w której uczestniczy ta jednostka. Metody HasRequired i HasOptional przyjmują wyrażenie lambda, które reprezentuje 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. Metody te 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ść do użycia jako klucz obcy.

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

W poniższym przykładzie skonfigurowana jest relacja jeden do zera 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 zgodne 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 oba końce są wymagane (jeden do jednego)

W większości przypadków program Entity Framework może wnioskować, który typ jest zależny i który jest podmiotem zabezpieczeń w relacji. Jeśli jednak oba końce relacji są wymagane lub obie strony są opcjonalne, program Entity Framework nie może 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 Kursu i Instruktora. W poniższym przykładzie domyślne konwencje Code First są używane do tworzenia tabeli sprzężenia. W wyniku utworzenia tabeli CourseInstructor 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 jednokierunkową relację 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 do skonfigurowania tej relacji.

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

Możesz skonfigurować usuwanie kaskadowe w relacji przy użyciu metody WillCascadeOnDelete. Jeśli klucz obcy jednostki zależnej nie może być zerowy, 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 konwencje usuwania kaskadowego można usunąć przy użyciu następujących metod:

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 Departamentu składa się z właściwości DepartmentID i Name, należy skonfigurować klucz podstawowy dla Działu i klucz obcy 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 zdefiniować 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 nazwę SomeDepartmentID zamiast identyfikatora 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; }
}