Fluent-API – Beziehungen

Hinweis

Diese Seite enthält Informationen zum Einrichten von Beziehungen in Ihrem Code First-Modell mithilfe der Fluent-API. Allgemeine Informationen zu Beziehungen in EF und zum Zugreifen auf und Bearbeiten von Daten mithilfe von Beziehungen finden Sie unter Beziehungen & Navigationseigenschaften.

Wenn Sie mit Code First arbeiten, definieren Sie Ihr Modell durch Definition Ihrer Domänen-CLR-Klassen. Standardmäßig verwendet Entity Framework die Code First-Konventionen, um Ihre Klassen dem Datenbankschema zuzuordnen. Wenn Sie die Code First-Namenskonventionen verwenden, können Sie sich in den meisten Fällen auf Code First verlassen, um Beziehungen zwischen Ihren Tabellen basierend auf den Fremdschlüsseln und Navigationseigenschaften einzurichten, die Sie für die Klassen definieren. Wenn Sie die Konventionen beim Definieren Ihrer Klassen nicht befolgen oder die Funktionsweise der Konventionen ändern möchten, können Sie die Fluent-API oder Datenanmerkungen verwenden, um Ihre Klassen so zu konfigurieren, dass Code First die Beziehungen zwischen Ihren Tabellen zuordnen kann.

Einführung

Beim Konfigurieren einer Beziehung mit der Fluent-API beginnen Sie mit der EntityTypeConfiguration-Instanz und verwenden dann die HasRequired-, HasOptional- oder HasMany-Methode, um den Typ der Beziehung anzugeben, an der diese Entität teilnimmt. Die HasRequired- und die HasOptional-Methode verwenden einen Lambdaausdruck, der eine Referenznavigationseigenschaft darstellt. Die HasMany-Methode verwendet einen Lambdaausdruck, der eine Sammlungsnavigationseigenschaft darstellt. Anschließend können Sie eine umgekehrte Navigationseigenschaft mithilfe der WithRequired-, WithOptional- und WithMany-Methoden konfigurieren. Diese Methoden weisen Überladungen auf, die keine Argumente annehmen und zum Angeben der Kardinalität mit unidirektionalen Navigationen verwendet werden können.

Anschließend können Sie Fremdschlüsseleigenschaften mithilfe der HasForeignKey-Methode konfigurieren. Diese Methode verwendet einen Lambdaausdruck, der die Eigenschaft darstellt, die als Fremdschlüssel verwendet werden soll.

Konfigurieren einer Beziehung mit einem erforderlichen und einem optionalen Ende (1:0/1)

Im folgenden Beispiel wird eine 1:0/1-Beziehung konfiguriert. OfficeAssignment verfügt über die InstructorID-Eigenschaft, bei der es sich um einen Primärschlüssel und einen Fremdschlüssel handelt. Da der Name der Eigenschaft nicht der Konvention folgt, wird die HasKey-Methode zum Konfigurieren des Primärschlüssels verwendet.

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

Konfigurieren einer 1:1-Beziehung (beide Enden sind erforderlich)

In den meisten Fällen kann Entity Framework die abhängige Entität und die Prinzipalentität in einer Beziehung bestimmen. Wenn jedoch beide Enden der Beziehung erforderlich sind oder beide Seiten optional sind, kann Entity Framework die abhängige Entität und die Prinzipalentität nicht identifizieren. Wenn beide Enden der Beziehung erforderlich sind, verwenden Sie WithRequiredPrincipal oder WithRequiredDependent nach der HasRequired-Methode. Wenn beide Enden der Beziehung optional sind, verwenden Sie WithOptionalPrincipal oder WithOptionalDependent nach der HasOptional-Methode.

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

Konfigurieren einer m:n-Beziehung

Der folgende Code konfiguriert eine m:n-Beziehung zwischen den Entitäten „Course” und „Instructor”. Im folgenden Beispiel werden die standardmäßigen Code First-Konventionen verwendet, um eine Verknüpfungstabelle zu erstellen. Folglich wird die Tabelle „CourseInstructor” mit den Spalten „Course_CourseID” und „Instructor_InstructorID” erstellt.

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

Wenn Sie den Verknüpfungstabellennamen und die Namen der Spalten in der Tabelle angeben möchten, müssen Sie mithilfe der Map-Methode zusätzliche Konfigurationen ausführen. Der folgende Code generiert die Tabelle „CourseInstructor” mit den Spalten „CourseID” und „InstructorID”.

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

Konfigurieren einer Beziehung mit einer Navigationseigenschaft

Eine eindirektionale (oder auch unidirektionale) Beziehung liegt vor, wenn eine Navigationseigenschaft nur für ein Ende der Beziehung und nicht für beide Enden definiert ist. Standardmäßig interpretiert Code First eine unidirektionale Beziehung immer als 1:n-Beziehung. Wenn Sie z. B. eine 1:1-Beziehung zwischen „Instructor” und „OfficeAssignment” erstellen möchten, bei der eine Navigationseigenschaft nur für „Instructor” definiert ist, müssen Sie die Fluent-API verwenden, um diese Beziehung zu konfigurieren.

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

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

Aktivieren der Löschweitergabe

Sie können die Löschweitergabe für eine Beziehung mithilfe der WillCascadeOnDelete-Methode konfigurieren. Wenn ein Fremdschlüssel für die abhängige Entität keine Nullwerte zulässt, legt Code First die Löschweitergabe für die Beziehung fest. Wenn ein Fremdschlüssel für die abhängige Entität Nullwerte zulässt, legt Code First keine Löschweitergabe für die Beziehung fest, und wenn der Prinzipal gelöscht wird, wird der Fremdschlüssel auf NULL festgelegt.

Sie können diese Konventionen für die Löschweitergabe entfernen, indem Sie Folgendes verwenden:

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

Der folgende Code konfiguriert die erforderliche Beziehung und deaktiviert dann die Löschweitergabe.

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

Konfigurieren eines zusammengesetzten Fremdschlüssels

Wenn der Primärschlüssel für „Department” aus den Eigenschaften „DepartmentID” und „Name” besteht, konfigurieren Sie den Primärschlüssel für „Department” und den Fremdschlüssel für „Course” wie folgt:

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

Umbenennen eines Fremdschlüssels, der im Modell nicht definiert ist

Wenn Sie sich entscheiden, keinen Fremdschlüssel für den CLR-Typ zu definieren, den Namen aber in der Datenbank angeben möchten, gehen Sie wie folgt vor:

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

Konfigurieren eines Fremdschlüsselnamens, der nicht der Code First-Konvention folgt

Wenn die Fremdschlüsseleigenschaft der Klasse „Course” den Namen „SomeDepartmentID” anstelle von „DepartmentID” trägt, müssen Sie Folgendes ausführen, um anzugeben, dass „SomeDepartmentID” der Fremdschlüssel sein soll:

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

In Beispielen verwendetes Modell

Das folgende Code First-Modell wird für die Beispiele auf dieser Seite verwendet.

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