Fluent API — konfigurowanie i mapowanie właściwości i typów

Podczas pracy z programem Entity Framework Code First zachowaniem domyślnym jest mapowanie klas POCO na tabele przy użyciu zestawu konwencji upieczonych w programie EF. Czasami jednak nie można lub nie chcesz przestrzegać tych konwencji i trzeba mapować jednostki na coś innego niż to, co dyktują konwencje.

Istnieją dwa główne sposoby konfigurowania platformy EF w celu używania czegoś innego niż konwencje, a mianowicie adnotacji lub płynnego interfejsu API EFs. Adnotacje obejmują tylko podzbiór płynnych funkcji interfejsu API, dlatego istnieją scenariusze mapowania, których nie można osiągnąć przy użyciu adnotacji. W tym artykule pokazano, jak używać płynnego interfejsu API do konfigurowania właściwości.

Dostęp do kodu pierwszego płynnego interfejsu API jest najczęściej uzyskiwany przez zastąpienie metody OnModelCreating w pochodnym obiekcie DbContext. Poniższe przykłady zostały zaprojektowane tak, aby pokazać, jak wykonywać różne zadania za pomocą płynnego interfejsu API i umożliwić skopiowanie kodu i dostosowanie go do modelu, jeśli chcesz zobaczyć model, z którym można używać zgodnie z rzeczywistym użyciem, na końcu tego artykułu.

Ustawienia dla całego modelu

Schemat domyślny (od wersji EF6)

Począwszy od ef6, możesz użyć metody HasDefaultSchema w dbModelBuilder, aby określić schemat bazy danych do użycia dla wszystkich tabel, procedur składowanych itp. To ustawienie domyślne zostanie zastąpione dla wszystkich obiektów jawnie skonfigurowanych dla innego schematu.

modelBuilder.HasDefaultSchema("sales");

Konwencje niestandardowe (od wersji EF6)

Począwszy od ef6, możesz utworzyć własne konwencje, aby uzupełnić te zawarte w Code First. Aby uzyskać więcej informacji, zobacz Niestandardowe konwencje Code First.

Mapowanie właściwości

Metoda Property służy do konfigurowania atrybutów dla każdej właściwości należącej do jednostki lub typu złożonego. Metoda Property służy do uzyskiwania obiektu konfiguracji dla danej właściwości. Opcje obiektu konfiguracji są specyficzne dla konfigurowanego typu; IsUnicode jest dostępny tylko we właściwościach ciągu, na przykład.

Konfigurowanie klucza podstawowego

Konwencja platformy Entity Framework dla kluczy podstawowych to:

  1. Klasa definiuje właściwość, której nazwa to "ID" lub "Id"
  2. lub nazwa klasy, po której następuje "ID" lub "Id"

Aby jawnie ustawić właściwość jako klucz podstawowy, możesz użyć metody HasKey. W poniższym przykładzie metoda HasKey służy do konfigurowania klucza podstawowego InstructorID w typie OfficeAssignment.

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

Konfigurowanie złożonego klucza podstawowego

Poniższy przykład umożliwia skonfigurowanie właściwości DepartmentID i Name jako klucza podstawowego złożonego typu Dział.

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

Wyłączanie tożsamości dla kluczy podstawowych liczbowych

Poniższy przykład ustawia właściwość DepartmentID na Wartość System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None, aby wskazać, że wartość nie zostanie wygenerowana przez bazę danych.

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

Określanie maksymalnej długości właściwości

W poniższym przykładzie właściwość Name nie powinna być dłuższa niż 50 znaków. Jeśli wartość będzie dłuższa niż 50 znaków, otrzymasz wyjątek DbEntityValidationException . Jeśli funkcja Code First utworzy bazę danych na podstawie tego modelu, ustawi również maksymalną długość kolumny Name na 50 znaków.

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

Konfigurowanie właściwości, która ma być wymagana

W poniższym przykładzie wymagana jest właściwość Name. Jeśli nie określisz nazwy, otrzymasz wyjątek DbEntityValidationException. Jeśli funkcja Code First utworzy bazę danych na podstawie tego modelu, kolumna używana do przechowywania tej właściwości będzie zwykle niepusta.

Uwaga

W niektórych przypadkach może nie być możliwe, aby kolumna w bazie danych była niepusta, mimo że właściwość jest wymagana. Na przykład w przypadku używania danych strategii dziedziczenia TPH dla wielu typów są przechowywane w jednej tabeli. Jeśli typ pochodny zawiera wymaganą właściwość, nie można utworzyć kolumny bez wartości null, ponieważ nie wszystkie typy w hierarchii będą miały tę właściwość.

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

Konfigurowanie indeksu dla co najmniej jednej właściwości

Uwaga

Tylko program EF6.1 — atrybut indeksu został wprowadzony w programie Entity Framework 6.1. Jeśli używasz starszej wersji, informacje przedstawione w tej sekcji nie mają zastosowania.

Tworzenie indeksów nie jest natywnie obsługiwane przez interfejs API Fluent, ale można korzystać z obsługi atrybutu IndexAttribute za pośrednictwem interfejsu API Fluent. Atrybuty indeksu są przetwarzane przez dołączenie adnotacji modelu do modelu, który następnie jest przekształcany w indeks w bazie danych w dalszej części potoku. Możesz ręcznie dodać te same adnotacje przy użyciu interfejsu API Fluent.

Najprostszym sposobem wykonania tej czynności jest utworzenie wystąpienia atrybutu IndexAttribute zawierającego wszystkie ustawienia dla nowego indeksu. Następnie można utworzyć wystąpienie funkcji IndexAnnotation , które jest typem specyficznym dla platformy EF, które przekonwertuje ustawienia IndexAttribute na adnotację modelu, która może być przechowywana w modelu EF. Następnie można je przekazać do metody HasColumnAnnotation w interfejsie API Fluent, określając nazwę Indeks adnotacji.

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

Aby uzyskać pełną listę ustawień dostępnych w elem. IndexAttribute, zobacz sekcję Indeks adnotacji code first data. Obejmuje to dostosowywanie nazwy indeksu, tworzenie unikatowych indeksów i tworzenie indeksów wielokolumnach.

Można określić wiele adnotacji indeksu w jednej właściwości, przekazując tablicę indexAttribute do konstruktora indeksuAnnotation.

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

Określanie, że nie ma mapować właściwości CLR na kolumnę w bazie danych

W poniższym przykładzie pokazano, jak określić, że właściwość typu CLR nie jest mapowana na kolumnę w bazie danych.

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

Mapowanie właściwości CLR na określoną kolumnę w bazie danych

Poniższy przykład mapuje właściwość Name CLR na kolumnę bazy danych DepartmentName.

modelBuilder.Entity<Department>()
    .Property(t => t.Name)
    .HasColumnName("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, czy właściwość string obsługuje zawartość Unicode

Domyślnie ciągi to Unicode (nvarchar w programie SQL Server). Możesz użyć metody IsUnicode, aby określić, że ciąg powinien być typu varchar.

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

Konfigurowanie typu danych kolumny bazy danych

Metoda HasColumnType umożliwia mapowanie na różne reprezentacje tego samego typu podstawowego. Użycie tej metody nie umożliwia wykonania żadnej konwersji danych w czasie wykonywania. Należy pamiętać, że isUnicode jest preferowanym sposobem ustawiania kolumn na wartość varchar, ponieważ jest niezależna od bazy danych.

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

Konfigurowanie właściwości w typie złożonym

Istnieją dwa sposoby konfigurowania właściwości skalarnych w typie złożonym.

Właściwość można wywołać w obiekcie ComplexTypeConfiguration.

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

Możesz również użyć notacji kropkowej, aby uzyskać dostęp do właściwości typu złożonego.

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

Konfigurowanie właściwości do użycia jako optymistyczny token współbieżności

Aby określić, że właściwość w jednostce reprezentuje token współbieżności, można użyć atrybutu ConcurrencyCheck lub metody IsConcurrencyToken.

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

Możesz również użyć metody IsRowVersion, aby skonfigurować właściwość jako wersję wiersza w bazie danych. Ustawienie właściwości jako wersji wiersza powoduje automatyczne skonfigurowanie jej jako optymistycznego tokenu współbieżności.

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

Mapowanie typów

Określanie, że klasa jest typem złożonym

Zgodnie z konwencją typ, który nie ma określonego klucza podstawowego, jest traktowany jako typ złożony. Istnieją pewne scenariusze, w których funkcja Code First nie wykryje typu złożonego (na przykład jeśli masz właściwość o nazwie ID, ale nie oznacza to, że jest to klucz podstawowy). W takich przypadkach należy użyć płynnego interfejsu API, aby jawnie określić, że typ jest typem złożonym.

modelBuilder.ComplexType<Details>();

Określanie niezmapowania typu jednostki CLR na tabelę w bazie danych

W poniższym przykładzie pokazano, jak wykluczyć typ CLR z mapowania na tabelę w bazie danych.

modelBuilder.Ignore<OnlineCourse>();

Mapowanie typu jednostki na określoną tabelę w bazie danych

Wszystkie właściwości działu zostaną zamapowane na kolumny w tabeli o nazwie t_ Department.

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

Możesz również określić nazwę schematu w następujący sposób:

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

Mapowanie dziedziczenia tabeli na hierarchię (TPH)

W scenariuszu mapowania TPH wszystkie typy w hierarchii dziedziczenia są mapowane na jedną tabelę. Kolumna dyskryminująca służy do identyfikowania typu każdego wiersza. Podczas tworzenia modelu za pomocą funkcji Code First funkcja TPH jest domyślną strategią dla typów uczestniczących w hierarchii dziedziczenia. Domyślnie do tabeli jest dodawana kolumna dyskryminująca o nazwie "Dyskryminująca", a nazwa typu CLR każdego typu w hierarchii jest używana dla wartości dyskryminujących. Domyślne zachowanie można zmodyfikować przy użyciu płynnego interfejsu API.

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

Mapowanie dziedziczenia tabeli na typ (TPT)

W scenariuszu mapowania TPT wszystkie typy są mapowane na poszczególne tabele. Właściwości należące wyłącznie do typu podstawowego lub pochodnego są przechowywane w tabeli, która jest mapowana na ten typ. Tabele mapujące na typy pochodne przechowują również klucz obcy, który łączy tabelę pochodną z tabelą bazową.

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

Mapowanie dziedziczenia klasy tabela-betonowej (TPC)

W scenariuszu mapowania TPC wszystkie nie abstrakcyjne typy w hierarchii są mapowane na poszczególne tabele. Tabele mapujące na klasy pochodne nie mają relacji z tabelą, która mapuje na klasę bazową w bazie danych. Wszystkie właściwości klasy, w tym właściwości dziedziczone, są mapowane na kolumny odpowiedniej tabeli.

Wywołaj metodę MapInheritedProperties, aby skonfigurować każdy typ pochodny. MapInheritedProperties ponownie mapuje wszystkie właściwości dziedziczone z klasy bazowej na nowe kolumny w tabeli dla klasy pochodnej.

Uwaga

Należy pamiętać, że ponieważ tabele uczestniczące w hierarchii dziedziczenia TPC nie współużytkują klucza podstawowego, podczas wstawiania w tabelach, które są mapowane na podklasy, jeśli baza danych wygenerowała wartości z tym samym inicjatorem tożsamości. Aby rozwiązać ten problem, możesz określić inną początkową wartość inicjatora dla każdej tabeli lub wyłączyć tożsamość we właściwości klucza podstawowego. Tożsamość jest wartością domyślną właściwości klucza całkowitego podczas pracy z funkcją 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");
});

Mapowanie właściwości typu jednostki na wiele tabel w bazie danych (dzielenie jednostek)

Dzielenie jednostek umożliwia rozłożenie właściwości typu jednostki na wiele tabel. W poniższym przykładzie jednostka Department (Dział) jest podzielona na dwie tabele: Department (Dział) i DepartmentDetails (Szczegóły działu). Dzielenie jednostek używa wielu wywołań metody Map, aby zamapować podzbiór właściwości na określoną tabelę.

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

Mapowanie wielu typów jednostek na jedną tabelę w bazie danych (dzielenie tabeli)

Poniższy przykład mapuje dwa typy jednostek, które współdzielą klucz podstawowy do jednej tabeli.

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

Mapowanie typu jednostki w celu wstawiania/aktualizowania/usuwania procedur składowanych (EF6)

Począwszy od ef6, można mapować jednostkę, aby używać procedur składowanych do wstawiania aktualizacji i usuwania. Aby uzyskać więcej informacji, zobacz Code First Insert/Update/Delete Stored Procedures (Procedury składowane Code First Insert/Update/Delete).

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