Fluent API – Konfigurace a mapování vlastností a typů

Při práci s Entity Framework Code First je výchozím chováním namapovat třídy POCO na tabulky pomocí sady konvencí pečených do EF. Někdy ale nemůžete nebo nechcete tyto konvence dodržovat a potřebujete namapovat entity na něco jiného než to, co konvence diktují.

Ef můžete nakonfigurovat dvěma hlavními způsoby, aby používal něco jiného než konvence, konkrétně poznámky nebo rozhraní API fluentu EFs. Poznámky pokrývají pouze podmnožinu fluentské funkce rozhraní API, takže existují scénáře mapování, které nelze dosáhnout pomocí poznámek. Tento článek je navržený tak, aby předvede, jak používat rozhraní FLUENT API ke konfiguraci vlastností.

K rozhraní API prvního fluentu kódu se nejčastěji přistupuje přepsáním metody OnModelCreating na odvozené DbContext. Následující ukázky jsou navržené tak, aby ukázaly, jak provádět různé úlohy s rozhraním FLUENT API, a umožnit vám zkopírovat kód a přizpůsobit ho tak, aby vyhovoval vašemu modelu, pokud chcete vidět model, se kterým je možné je použít, pak je k dispozici na konci tohoto článku.

Modelový Nastavení

Výchozí schéma (EF6 a novější)

Počínaje systémem EF6 můžete použít metodu HasDefaultSchema v DbModelBuilder k určení schématu databáze, které se má použít pro všechny tabulky, uložené procedury atd. Toto výchozí nastavení se přepíše pro všechny objekty, pro které explicitně nakonfigurujete jiné schéma.

modelBuilder.HasDefaultSchema("sales");

Vlastní konvence (EF6)

Od EF6 můžete vytvořit vlastní konvence, které doplňují ty, které jsou součástí code First. Další podrobnosti najdete v tématu Vlastní konvence Code First.

Mapování vlastností

Metoda Property slouží ke konfiguraci atributů pro každou vlastnost patřící entitě nebo komplexnímu typu. Metoda Property se používá k získání objektu konfigurace pro danou vlastnost. Možnosti objektu konfigurace jsou specifické pro typ, který se konfiguruje; IsUnicode je k dispozici pouze u vlastností řetězce, například.

Konfigurace primárního klíče

Konvence entity Framework pro primární klíče je:

  1. Vaše třída definuje vlastnost, jejíž název je ID nebo ID.
  2. nebo název třídy následovaný "ID" nebo "ID"

Pokud chcete explicitně nastavit vlastnost jako primární klíč, můžete použít Metodu HasKey. V následujícím příkladu se metoda HasKey používá ke konfiguraci primárního klíče InstructorID u typu OfficeAssignment.

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

Konfigurace složeného primárního klíče

Následující příklad nakonfiguruje vlastnosti DepartmentID a Name jako složený primární klíč typu Oddělení.

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

Vypnutí identity pro číselné primární klíče

Následující příklad nastaví DepartmentID vlastnost System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None na označení, že hodnota nebude generována databází.

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

Určení maximální délky vlastnosti

V následujícím příkladu by vlastnost Name neměla být delší než 50 znaků. Pokud nastavíte hodnotu delší než 50 znaků, získáte výjimku DbEntityValidationException . Pokud Code First vytvoří databázi z tohoto modelu, nastaví se také maximální délka sloupce Název na 50 znaků.

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

Konfigurace vlastnosti, která se má vyžadovat

V následujícím příkladu je požadována vlastnost Name. Pokud nezadáte název, získáte výjimku DbEntityValidationException. Pokud Code First vytvoří databázi z tohoto modelu, sloupec použitý k uložení této vlastnosti bude obvykle nenulový.

Poznámka

V některých případech nemusí být možné, aby sloupec v databázi byl nenulový, i když je vlastnost povinná. Například při použití dat strategie dědičnosti TPH pro více typů jsou uložena v jedné tabulce. Pokud odvozený typ obsahuje požadovanou vlastnost, sloupec nelze vytvořit bez hodnoty null, protože ne všechny typy v hierarchii budou mít tuto vlastnost.

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

Konfigurace indexu pro jednu nebo více vlastností

Poznámka

POUZE EF6.1 – Atribut indexu byl zaveden v entity Framework 6.1. Pokud používáte starší verzi, informace v této části se nevztahují.

Rozhraní Fluent API nativně nepodporuje vytváření indexů, ale můžete využít podporu indexAttribute prostřednictvím rozhraní Fluent API. Atributy indexu se zpracovávají zahrnutím poznámky modelu do modelu, který se pak později v kanálu změní na index v databázi. Stejné poznámky můžete přidat ručně pomocí rozhraní Fluent API.

Nejjednodušším způsobem je vytvořit instanci IndexAttribute , která obsahuje všechna nastavení pro nový index. Pak můžete vytvořit instanci IndexAnnotation , což je specifický typ EF, který převede nastavení IndexAttribute na poznámku modelu, která se dá uložit do modelu EF. Ty je pak možné předat metodě HasColumnAnnotation v rozhraní Fluent API a zadat název Index pro anotaci.

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

Úplný seznam nastavení dostupných v IndexAttribute najdete v části Index First DataNots (První datové poznámky kódu). To zahrnuje přizpůsobení názvu indexu, vytváření jedinečných indexů a vytváření indexů s více sloupci.

Pro jednu vlastnost můžete zadat více poznámek indexu předáním pole IndexAttribute konstruktoru IndexAnnotation.

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

Určení nemapování vlastnosti CLR na sloupec v databázi

Následující příklad ukazuje, jak určit, že vlastnost typu CLR není mapována na sloupec v databázi.

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

Mapování vlastnosti CLR na konkrétní sloupec v databázi

Následující příklad mapuje vlastnost Name CLR na sloupec databáze DepartmentName.

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

Přejmenování cizího klíče, který není definován v modelu

Pokud se rozhodnete nedefinovat cizí klíč pro typ CLR, ale chcete zadat, jaký název by měl mít v databázi, postupujte takto:

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

Konfigurace, zda vlastnost řetězce podporuje obsah Unicode

Ve výchozím nastavení jsou řetězce Unicode (nvarchar v SQL Serveru). Metodu IsUnicode můžete použít k určení, že řetězec má být typu varchar.

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

Konfigurace datového typu databázového sloupce

Metoda HasColumnType umožňuje mapování na různé reprezentace stejného základního typu. Použití této metody neumožňuje provádět žádné převody dat za běhu. Všimněte si, že IsUnicode je upřednostňovaným způsobem nastavení sloupců na varchar, protože je nezávislá na databázi.

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

Konfigurace vlastností komplexního typu

Skalární vlastnosti v komplexním typu lze konfigurovat dvěma způsoby.

Vlastnost můžete volat v ComplexTypeConfiguration.

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

K přístupu k vlastnosti komplexního typu můžete také použít tečku.

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

Konfigurace vlastnosti, která se má použít jako optimistický token souběžnosti

Chcete-li určit, že vlastnost v entitě představuje token souběžnosti, můžete použít buď ConcurrencyCheck atribut, nebo IsConcurrencyToken metoda.

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

Můžete také použít IsRowVersion metoda ke konfiguraci vlastnosti být verze řádku v databázi. Nastavení vlastnosti na verzi řádku automaticky nakonfiguruje jako optimistický token souběžnosti.

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

Mapování typů

Určení, že třída je komplexní typ

Podle konvence se typ, který nemá zadaný žádný primární klíč, považuje za komplexní typ. Existuje několik scénářů, kdy Code First nerozpozná komplexní typ (například pokud máte vlastnost s názvem ID, ale neznamená to, že se jedná o primární klíč). V takových případech byste pomocí rozhraní API fluent explicitně určili, že typ je složitý typ.

modelBuilder.ComplexType<Details>();

Určení nemapování typu entity CLR na tabulku v databázi

Následující příklad ukazuje, jak vyloučit typ CLR z mapování na tabulku v databázi.

modelBuilder.Ignore<OnlineCourse>();

Mapování typu entity na konkrétní tabulku v databázi

Všechny vlastnosti oddělení budou mapovány na sloupce v tabulce s názvem t_ Department.

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

Můžete také zadat název schématu takto:

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

Mapování dědičnosti TPH (Table-Per-Hierarchy)

Ve scénáři mapování TPH se všechny typy v hierarchii dědičnosti mapují na jednu tabulku. K identifikaci typu každého řádku se používá nediskriminační sloupec. Při vytváření modelu pomocí code First je TPH výchozí strategií pro typy, které se účastní hierarchie dědičnosti. Ve výchozím nastavení se do tabulky přidá nediskriminační sloupec s názvem "Diskriminátor" a pro diskriminující hodnoty se použije název typu CLR každého typu v hierarchii. Výchozí chování můžete upravit pomocí rozhraní API fluent.

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

Mapování dědičnosti tabulek na typ (TPT)

Ve scénáři mapování TPT se všechny typy mapují na jednotlivé tabulky. Vlastnosti, které patří výhradně do základního nebo odvozeného typu, jsou uloženy v tabulce, která se mapuje na tento typ. Tabulky mapované na odvozené typy také ukládají cizí klíč, který spojuje odvozenou tabulku se základní tabulkou.

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

Mapování dědičnosti třídy TPC (Table-Per-Concrete Class)

Ve scénáři mapování TPC se všechny ne abstraktní typy v hierarchii mapují na jednotlivé tabulky. Tabulky mapované na odvozené třídy nemají žádnou relaci s tabulkou, která se mapuje na základní třídu v databázi. Všechny vlastnosti třídy, včetně zděděných vlastností, jsou mapovány na sloupce odpovídající tabulky.

Volání MapInheritedProperties metoda konfigurovat každý odvozený typ. MapInheritedProperties přemapuje všechny vlastnosti, které byly zděděny ze základní třídy do nových sloupců v tabulce pro odvozenou třídu.

Poznámka

Všimněte si, že protože tabulky, které se účastní hierarchie dědičnosti TPC, nesdílejí primární klíč, budou při vkládání do tabulek mapovaných na podtřídy, pokud máte vygenerované hodnoty databáze se stejným počátečním datem identity, duplicitní klíče entit. Chcete-li tento problém vyřešit, můžete zadat jinou počáteční počáteční hodnotu pro každou tabulku nebo vypnout identitu u vlastnosti primárního klíče. Identita je výchozí hodnota vlastností celočíselného klíče při práci s 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");
});

Mapování vlastností typu entity na více tabulek v databázi (rozdělení entit)

Rozdělení entit umožňuje rozdělit vlastnosti typu entity do více tabulek. V následujícím příkladu je entita Oddělení rozdělena do dvou tabulek: Oddělení a DepartmentDetails. Rozdělení entit používá více volání metody Map k mapování podmnožinu vlastností na konkrétní tabulku.

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

Mapování více typů entit na jednu tabulku v databázi (rozdělení tabulky)

Následující příklad mapuje dva typy entit, které sdílejí primární klíč do jedné tabulky.

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

Mapování typu entity na vložení, aktualizaci nebo odstranění uložených procedur (EF6)

Od EF6 můžete namapovat entitu tak, aby používala uložené procedury pro vložení aktualizace a odstranění. Další podrobnosti najdete v části První vložení, aktualizace nebo odstranění uložených procedur v kódu.

Model použitý v ukázkách

Následující model Code First se používá pro ukázky na této stránce.

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