Teilen über


Fluent-API – Konfigurieren und Zuordnen von Eigenschaften und Typen

Beim Arbeiten mit Entity Framework Code First besteht das Standardverhalten darin, Ihre POCO-Klassen Tabellen mithilfe einer Reihe von Konventionen zuzuordnen, die in EF integriert sind. Manchmal können oder wollen Sie sich jedoch nicht an diese Konventionen halten und müssen Entitäten etwas anderem zuordnen, als es die Konventionen vorschreiben.

Es gibt zwei Möglichkeiten, EF so zu konfigurieren, dass es etwas anderes als Konventionen verwendet, nämlich Anmerkungen oder die Fluent-API von EF. Die Anmerkungen decken nur eine Teilmenge der Fluent-API-Funktionalität ab, daher gibt es Zuordnungsszenarien, die nicht mithilfe von Anmerkungen erreicht werden können. Dieser Artikel soll Ihnen zeigen, wie Sie die Fluent-API zur Konfiguration von Eigenschaften verwenden können.

Die Code First Fluent-API wird am häufigsten aufgerufen, indem die OnModelCreating-Methode auf Ihrem abgeleiteten DbContext überschrieben wird. Die folgenden Beispiele sollen Ihnen zeigen, wie Sie verschiedene Aufgaben mit der Fluent-API erledigen können. Sie können den Code kopieren und an Ihr Modell anpassen, wenn Sie das Modell sehen möchten, mit dem diese Beispiele verwendet werden können.

Modellweite Einstellungen

Standardschema (ab EF6)

Ab EF6 können Sie die HasDefaultSchema-Methode auf DbModelBuilder verwenden, um das Datenbankschema anzugeben, das für alle Tabellen, gespeicherten Prozeduren usw. verwendet werden soll. Diese Standardeinstellung wird für alle Objekte außer Kraft gesetzt, für die Sie explizit ein anderes Schema konfigurieren.

modelBuilder.HasDefaultSchema("sales");

Benutzerdefinierte Konventionen (ab EF6)

Ab EF6 können Sie eigene Konventionen erstellen, um die in Code First enthaltenen Konventionen zu ergänzen. Weitere Informationen finden Sie unter Benutzerdefinierte Code First-Konventionen.

Eigenschaftszuordnung

Die Property-Methode wird verwendet, um Attribute für jede Eigenschaft zu konfigurieren, die zu einer Entität oder einem komplexen Typ gehört. Die Property-Methode wird verwendet, um ein Konfigurationsobjekt für eine bestimmte Eigenschaft abzurufen. Die Optionen für das Konfigurationsobjekt sind spezifisch für den Typ, der konfiguriert wird; IsUnicode ist beispielsweise nur für Zeichenfolgeneigenschaften verfügbar.

Konfigurieren eines Primärschlüssels

Die Entity Framework-Konvention für Primärschlüssel lautet:

  1. Ihre Klasse definiert eine Eigenschaft, deren Name „ID“ oder „Id“ ist
  2. oder einen Klassennamen gefolgt von „ID“ oder „Id“.

Um eine Eigenschaft explizit als Primärschlüssel festzulegen, können Sie die HasKey-Methode verwenden. Im folgenden Beispiel wird die HasKey-Methode verwendet, um den InstructorID-Primärschlüssel für den OfficeAssignment-Typ zu konfigurieren.

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

Konfigurieren eines zusammengesetzten Primärschlüssels

Im folgenden Beispiel werden die Eigenschaften DepartmentID und Name als zusammengesetzter Primärschlüssel des Abteilungstyps konfiguriert.

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

Deaktivieren der Identität für numerische Primärschlüssel

Im folgenden Beispiel wird die DepartmentID-Eigenschaft auf System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None festgelegt, um anzugeben, dass der Wert nicht von der Datenbank generiert wird.

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

Angeben der maximalen Länge für eine Eigenschaft

Im folgenden Beispiel sollte die Name-Eigenschaft nicht mehr als 50 Zeichen lang sein. Wenn Sie den Wert auf länger als 50 Zeichen festlegen, erhalten Sie eine DbEntityValidationException-Ausnahme. Wenn Code First eine Datenbank aus diesem Modell erstellt, wird auch die maximale Länge der Spalte „Name“ auf 50 Zeichen festgelegt.

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

Konfigurieren der Eigenschaft als erforderlich

Im folgenden Beispiel ist die Name-Eigenschaft erforderlich. Wenn Sie den Namen nicht angeben, erhalten Sie eine DbEntityValidationException-Ausnahme. Wenn Code First eine Datenbank aus diesem Modell erstellt, ist die Spalte, die zum Speichern dieser Eigenschaft verwendet wird, in der Regel non-nullable.

Hinweis

In einigen Fällen ist es möglicherweise nicht möglich, dass die Spalte in der Datenbank keine Nullwerte zulassen kann, obwohl die Eigenschaft erforderlich ist. Wenn z. B. eine TPH-Vererbungsstrategie verwendet wird, werden Daten für mehrere Typen in einer einzelnen Tabelle gespeichert. Wenn ein abgeleiteter Typ eine erforderliche Eigenschaft enthält, kann nicht festgelegt werden, dass die Spalte keine Nullwerte zulassen kann, da nicht alle Typen in der Hierarchie diese Eigenschaft aufweisen werden.

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

Konfigurieren eines Indexes für eine oder mehrere Eigenschaften

Hinweis

Nur ab EF6.1: Das Index-Attribut wurde in Entity Framework 6.1 eingeführt. Wenn Sie eine frühere Version verwenden, gelten die Informationen in diesem Abschnitt nicht.

Das Erstellen von Indizes wird von der Fluent-API nicht nativ unterstützt, Sie können jedoch die Unterstützung für IndexAttribute über die Fluent-API verwenden. Indexattribute werden verarbeitet, indem eine Modellanmerkung in das Modell eingeschlossen wird, das dann später in der Pipeline in einen Index umgewandelt wird. Sie können diese Anmerkungen manuell mithilfe der Fluent-API hinzufügen.

Am einfachsten können Sie dazu eine Instanz von IndexAttribute erstellen, die alle Einstellungen für den neuen Index enthält. Anschließend können Sie eine Instanz von IndexAnnotation erstellen, bei der es sich um einen EF-spezifischen Typ handelt, der die IndexAttribute-Einstellungen in eine Modellanmerkung konvertiert, die im EF-Modell gespeichert werden kann. Diese können dann an die HasColumnAnnotation-Methode der Fluent-API übergeben werden, wobei der Name Index für die Anmerkung angegeben wird.

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

Eine vollständige Liste der in IndexAttribute verfügbaren Einstellungen finden Sie im Abschnitt Index der Code First-Datenanmerkungen. Dazu gehört das Anpassen des Indexnamens, das Erstellen eindeutiger Indizes und das Erstellen von mehrspaltigen Indizes.

Sie können mehrere Indexanmerkungen für eine einzelne Eigenschaft angeben, indem Sie ein Array von IndexAttribute an den Konstruktor IndexAnnotation übergeben.

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

Angeben, dass eine CLR-Eigenschaft nicht einer Spalte in der Datenbank zugeordnet werden soll

Das folgende Beispiel zeigt, wie Sie angeben, dass eine Eigenschaft eines CLR-Typs keiner Spalte in der Datenbank zugeordnet ist.

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

Zuordnen einer CLR-Eigenschaft zu einer bestimmten Spalte in der Datenbank

Im folgenden Beispiel wird die Name CLR-Eigenschaft der Spalte „DepartmentName“-Datenbank zugeordnet.

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

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

Wenn Sie sich entscheiden, keinen Fremdschlüssel für einen 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, ob eine String-Eigenschaft Unicode-Inhalte unterstützt

Standardmäßig sind Zeichenfolgen Unicode (nvarchar in SQL Server). Mit der IsUnicode-Methode können Sie angeben, dass eine Zeichenfolge den Typ varchar haben soll.

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

Konfigurieren des Datentyps der Datenbankspalte

Mit der HasColumnType-Methode können verschiedene Darstellungen desselben Basistyps zugeordnet werden. Wenn Sie diese Methode verwenden, können Sie zur Laufzeit keine Konvertierung der Daten durchführen. Beachten Sie, dass IsUnicode die bevorzugte Methode zum Festlegen von Spalten auf varchar ist, da sie datenbankunabhängig ist.

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

Konfigurieren von Eigenschaften für einen komplexen Typ

Es gibt zwei Möglichkeiten, skalare Eigenschaften für einen komplexen Typ zu konfigurieren.

Sie können Property auf ComplexTypeConfiguration aufrufen.

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

Sie können auch die Punktnotation verwenden, um auf eine Eigenschaft eines komplexen Typs zuzugreifen.

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

Konfigurieren einer Eigenschaft, die als Token für vollständige Parallelität verwendet werden soll

Um anzugeben, dass eine Eigenschaft in einer Entität ein Parallelitätstoken darstellt, können Sie entweder das ConcurrencyCheck-Attribut oder die IsConcurrencyToken-Methode verwenden.

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

Sie können auch die IsRowVersion-Methode verwenden, um die Eigenschaft als Zeilenversion in der Datenbank zu konfigurieren. Durch Festlegen der Eigenschaft auf eine Zeilenversion wird die Eigenschaft automatisch als Token für vollständige Parallelität konfiguriert.

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

Typzuordnung

Festlegen, dass eine Klasse ein komplexer Typ ist

Standardmäßig wird ein Typ, der keinen Primärschlüssel angegeben hat, als komplexer Typ behandelt. Es gibt einige Szenarien, in denen Code First keinen komplexen Typ erkennt (z. B. wenn Sie eine Eigenschaft namens „ID“ haben, was aber nicht bedeutet, dass es sich um einen Primärschlüssel handelt). In solchen Fällen würden Sie die Fluent-API verwenden, um explizit anzugeben, dass ein Typ ein komplexer Typ ist.

modelBuilder.ComplexType<Details>();

Festlegen, dass kein CLR-Entitätstyp einer Tabelle in der Datenbank zugeordnet werden soll

Das folgende Beispiel zeigt, wie Sie einen CLR-Typ ausschließen, der einer Tabelle in der Datenbank zugeordnet wird.

modelBuilder.Ignore<OnlineCourse>();

Zuordnen eines Entitätstyps zu einer bestimmten Tabelle in der Datenbank

Alle Eigenschaften der Abteilung werden Spalten in einer Tabelle namens t_ Department zugeordnet.

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

Sie können auch den Schemanamen wie folgt angeben:

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

Zuordnen der Tabelle pro Hierarchie (TPH)-Vererbung

Im TPH-Zuordnungsszenario werden alle Typen in einer Vererbungshierarchie einer einzelnen Tabelle zugeordnet. Eine Diskriminatorspalte wird verwendet, um den Typ jeder Zeile zu identifizieren. Beim Erstellen des Modells mit Code First ist TPH die Standardstrategie für die Typen, die an der Vererbungshierarchie teilnehmen. Standardmäßig wird die Diskriminatorspalte der Tabelle mit dem Namen ““Discriminator“ hinzugefügt, und der CLR-Typname jedes Typs in der Hierarchie wird für die Diskriminatorwerte verwendet. Sie können das Standardverhalten mithilfe der Fluent-API ändern.

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

Zuordnen der Tabelle-pro-Typ (TPT)-Vererbung

Im TPT-Zuordnungsszenario werden alle Typen einzelnen Tabellen zugeordnet. Eigenschaften, die nur zu einem Basistyp oder einem abgeleiteten Typ gehören, werden in einer Tabelle gespeichert, die diesem Typ zugeordnet ist. Tabellen, die abgeleiteten Typen zugeordnet sind, speichern auch einen Fremdschlüssel, der die abgeleitete Tabelle mit der Basistabelle verknüpft.

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

Zuordnen der Tabelle pro konkrete Klasse (TPC)-Vererbung

Im TPC-Zuordnungsszenario werden alle nicht abstrakten Typen in der Hierarchie einzelnen Tabellen zugeordnet. Die Tabellen, die den abgeleiteten Klassen zugeordnet sind, haben keine Beziehung zur Tabelle, die der Basisklasse in der Datenbank zugeordnet ist. Alle Eigenschaften einer Klasse, einschließlich der geerbten Eigenschaften, werden Spalten der entsprechenden Tabelle zugeordnet.

Rufen Sie die MapInheritedProperties-Methode auf, um jeden abgeleiteten Typ zu konfigurieren. MapInheritedProperties ordnet alle Eigenschaften, die von der Basisklasse geerbt wurden, zu neuen Spalten in der Tabelle für die abgeleitete Klasse neu zu.

Hinweis

Da die an der TPC-Vererbungshierarchie beteiligten Tabellen keinen gemeinsamen Primärschlüssel haben, kommt es beim Einfügen in Tabellen, die Unterklassen zugeordnet sind, zu doppelten Entity-Schlüsseln, wenn Sie datenbankgenerierte Werte mit demselben Identitätsschlüssel haben. Um dieses Problem zu beheben, können Sie entweder einen anderen Anfangswert für jede Tabelle angeben oder die Identität für die Primärschlüsseleigenschaft deaktivieren. Identity ist der Standardwert für ganzzahlige Schlüsseleigenschaften beim Arbeiten mit 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");
});

Zuordnen von Eigenschaften eines Entitätstyps zu mehreren Tabellen in der Datenbank (Entitätsaufteilung)

Durch die Entitätsaufteilung können die Eigenschaften eines Entitätstyps auf mehrere Tabellen verteilt werden. Im folgenden Beispiel wird die Abteilungsentität in zwei Tabellen unterteilt: Department und DepartmentDetails. Die Entitätsaufteilung verwendet mehrere Aufrufe der Zuordnungsmethode, um einer bestimmten Tabelle eine Teilmenge von Eigenschaften zuzuordnen.

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

Zuordnen mehrerer Entitätstypen zu einer Tabelle in der Datenbank (Tabellenaufteilung)

Im folgenden Beispiel werden zwei Entitätstypen zugeordnet, die einen Primärschlüssel gemeinsam mit einer Tabelle verwenden.

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

Zuordnen eines Entitätstyps zum Einfügen/Aktualisieren/Löschen gespeicherter Prozeduren (ab EF6)

Ab EF6 können Sie eine Entität zuordnen, um gespeicherte Prozeduren zum Einfügen von Aktualisierungen und Löschen zu verwenden. Weitere Informationen finden Sie unter In Code First gespeicherte Prozeduren zum Einfügen/Aktualisieren/Löschen.

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