Fluent API – Konfigurera och mappa egenskaper och typer

När du arbetar med Entity Framework Code Först är standardbeteendet att mappa dina POCO-klasser till tabeller med hjälp av en uppsättning konventioner som bakats in i EF. Ibland kan du dock inte eller vill inte följa dessa konventioner och behöver mappa entiteter till något annat än vad konventionerna dikterar.

Det finns två huvudsakliga sätt att konfigurera EF att använda något annat än konventioner, nämligen anteckningar eller EFs fluent API. Anteckningarna täcker bara en delmängd av funktionerna i api:et fluent, så det finns mappningsscenarier som inte kan uppnås med hjälp av anteckningar. Den här artikeln är utformad för att visa hur du använder api:et fluent för att konfigurera egenskaper.

Code First Fluent API används oftast genom att åsidosätta metoden OnModelCreating i din härledda DbContext. Följande exempel är utformade för att visa hur du utför olika uppgifter med api:et fluent och gör att du kan kopiera ut koden och anpassa den efter din modell, om du vill se den modell som de kan användas med as-is så tillhandahålls den i slutet av den här artikeln.

Model-Wide inställningar

Standardschema (EF6 och senare)

Från och med EF6 kan du använda metoden HasDefaultSchema i DbModelBuilder för att ange det databasschema som ska användas för alla tabeller, lagrade procedurer osv. Den här standardinställningen åsidosätts för alla objekt som du uttryckligen konfigurerar ett annat schema för.

modelBuilder.HasDefaultSchema("sales");

Anpassade konventioner (EF6 och senare)

Från och med EF6 kan du skapa egna konventioner för att komplettera de som ingår i Code First. Mer information finns i De första konventionerna för anpassad kod.

Egenskapsmappning

Metoden Egenskap används för att konfigurera attribut för varje egenskap som tillhör en entitet eller komplex typ. Metoden Egenskap används för att hämta ett konfigurationsobjekt för en viss egenskap. Alternativen för konfigurationsobjektet är specifika för den typ som konfigureras. IsUnicode är endast tillgängligt för strängegenskaper, till exempel.

Konfigurera en primärnyckel

Entity Framework-konventionen för primära nycklar är:

  1. Klassen definierar en egenskap vars namn är "ID" eller "Id"
  2. eller ett klassnamn följt av "ID" eller "Id"

Om du uttryckligen vill ange att en egenskap ska vara en primärnyckel kan du använda metoden HasKey. I följande exempel används Metoden HasKey för att konfigurera den primära InstructorID-nyckeln för OfficeAssignment-typen.

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

Konfigurera en sammansatt primärnyckel

I följande exempel konfigureras egenskaperna DepartmentID och Name som den sammansatta primärnyckeln för avdelningstypen.

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

Stänga av identitet för numeriska primära nycklar

I följande exempel anges egenskapen DepartmentID till System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None för att indikera att värdet inte genereras av databasen.

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

Ange maximal längd för en egenskap

I följande exempel ska egenskapen Namn inte vara längre än 50 tecken. Om du gör värdet längre än 50 tecken får du ett DbEntityValidationException-undantag . Om Code First skapar en databas från den här modellen kommer den också att ange den maximala längden på kolumnen Namn till 50 tecken.

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

Konfigurera egenskapen som obligatorisk

I följande exempel krävs egenskapen Namn. Om du inte anger namnet får du ett DbEntityValidationException-undantag. Om Code First skapar en databas från den här modellen är den kolumn som används för att lagra den här egenskapen vanligtvis inte nullbar.

Anmärkning

I vissa fall är det kanske inte möjligt för kolumnen i databasen att vara icke-nullbar trots att egenskapen krävs. När du till exempel använder en TPH-arvsstrategi lagras data för flera typer i en enda tabell. Om en härledd typ innehåller en obligatorisk egenskap kan kolumnen inte göras icke-nullbar eftersom inte alla typer i hierarkin har den här egenskapen.

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

Konfigurera ett index för en eller flera egenskaper

Anmärkning

EF6.1 Endast framåt – Indexattributet introducerades i Entity Framework 6.1. Om du använder en tidigare version gäller inte informationen i det här avsnittet.

Att skapa index stöds inte internt av Fluent API, men du kan använda stödet för IndexAttribute via Fluent API. Indexattribut bearbetas genom att inkludera en modellanteckning för modellen som sedan omvandlas till ett index i databasen senare i pipelinen. Du kan lägga till samma anteckningar manuellt med hjälp av Fluent API.

Det enklaste sättet att göra detta är att skapa en instans av IndexAttribute som innehåller alla inställningar för det nya indexet. Du kan sedan skapa en instans av IndexAnnotation som är en EF-specifik typ som konverterar inställningarna för IndexAttribute till en modellanteckning som kan lagras i EF-modellen. Dessa kan sedan skickas till metoden HasColumnAnnotation i Fluent-API:et och ange namnet Index för anteckningen.

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

En fullständig lista över de inställningar som är tillgängliga i IndexAttribute finns i avsnittet Index i Koda första dataanteckningar. Detta omfattar anpassning av indexnamnet, skapande av unika index och skapande av flerkolumnsindex.

Du kan ange flera indexanteckningar på en enda egenskap genom att skicka en matris med IndexAttribute till konstruktorn för IndexAnnotation.

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

Ange att en CLR-egenskap inte ska mappas till en kolumn i databasen

I följande exempel visas hur du anger att en egenskap för en CLR-typ inte mappas till en kolumn i databasen.

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

Mappa en CLR-egenskap till en specifik kolumn i databasen

I följande exempel mappas CLR-egenskapen Namn till databaskolumnen DepartmentName.

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

Byta namn på en sekundärnyckel som inte har definierats i modellen

Om du väljer att inte definiera en sekundärnyckel för en CLR-typ, men vill ange vilket namn den ska ha i databasen, gör du följande:

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

Konfigurera om en strängegenskap stöder Unicode-innehåll

Som standard är strängar Unicode (nvarchar i SQL Server). Du kan använda metoden IsUnicode för att ange att en sträng ska vara av varchar-typ.

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

Konfigurera datatypen för en databaskolumn

Metoden HasColumnType möjliggör mappning till olika representationer av samma grundläggande typ. Med den här metoden kan du inte utföra någon konvertering av data i realtid. Observera att IsUnicode är det bästa sättet att ange kolumner till varchar, eftersom det är databasagnostiskt.

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

Konfigurera egenskaper för en komplex typ

Det finns två sätt att konfigurera skalära egenskaper på en komplex typ.

Du kan anropa egenskapen på ComplexTypeConfiguration.

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

Du kan också använda punkt notationen för att komma åt en egenskap av en komplex typ.

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

Konfigurera en egenskap som ska användas som en optimistisk samtidighetstoken

Om du vill ange att en egenskap i en entitet representerar en samtidighetstoken kan du använda antingen attributet ConcurrencyCheck eller metoden IsConcurrencyToken.

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

Du kan också använda metoden IsRowVersion för att konfigurera egenskapen till en radversion i databasen. Om du anger egenskapen till en radversion konfigureras den automatiskt som en optimistisk samtidighetstoken.

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

Typmappning

Ange att en klass är en komplex typ

Enligt konventionen behandlas en typ som inte har någon primärnyckel som en komplex typ. Det finns vissa scenarier där Code First inte identifierar en komplex typ (till exempel om du har en egenskap som kallas ID, men du inte menar att det ska vara en primärnyckel). I sådana fall använder du api:et fluent för att uttryckligen ange att en typ är en komplex typ.

modelBuilder.ComplexType<Details>();

Ange att en CLR-entitetstyp inte ska mappas till en tabell i databasen

I följande exempel visas hur du undantar en CLR-typ från att mappas till en tabell i databasen.

modelBuilder.Ignore<OnlineCourse>();

Mappa en entitetstyp till en specifik tabell i databasen

Alla egenskaper för avdelning mappas till kolumner i en tabell som heter t_ Avdelning.

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

Du kan också ange schemanamnet så här:

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

Kartläggning av arv med table-per-hierarchy (TPH)

I scenariot för TPH-mappning mappas alla typer i en arvshierarki till en enda tabell. En diskriminerande kolumn används för att identifiera typen av varje rad. När du skapar din modell med Code First är TPH standardstrategin för de typer som deltar i arvshierarkin. Som standard läggs den diskriminerande kolumnen till i tabellen med namnet "Discriminator" och CLR-typnamnet för varje typ i hierarkin används för de diskriminerande värdena. Du kan ändra standardbeteendet med hjälp av API:et fluent.

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

Kartläggning av Table-Per-Type (TPT) arv

I scenariot för TPT-mappning mappas alla typer till enskilda tabeller. Egenskaper som endast tillhör en bastyp eller härledd typ lagras i en tabell som mappar till den typen. Tabeller som mappar till härledda typer lagrar också en sekundärnyckel som kopplar den härledda tabellen till bastabellen.

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

Mappa Table-Per-Concrete Class (TPC) ärvning

I scenariot för TPC-mappning mappas alla icke-abstrakta typer i hierarkin till enskilda tabeller. Tabellerna som mappar till de härledda klasserna har ingen relation till tabellen som mappar till basklassen i databasen. Alla egenskaper för en klass, inklusive ärvda egenskaper, mappas till kolumner i motsvarande tabell.

Anropa metoden MapInheritedProperties för att konfigurera varje härledd typ. MapInheritedProperties mappar om alla egenskaper som ärvts från basklassen till nya kolumner i tabellen för den härledda klassen.

Anmärkning

Observera att eftersom tabellerna som ingår i TPC-arvshierarkin inte delar en primärnyckel kommer det att finnas duplicerade entitetsnycklar när du infogar i tabeller som mappas till underklasser om du har databasgenererade värden med samma identitetsutdata. För att lösa det här problemet kan du antingen ange ett annat startvärde för varje tabell eller stänga av identiteten för primärnyckelegenskapen. Identitet är standardvärdet för heltalsnyckelegenskaper när du arbetar med 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");
});

Mappa egenskaper för en entitetstyp till flera tabeller i databasen (entitetsdelning)

Entitetsdelning gör att egenskaperna för en entitetstyp kan spridas över flera tabeller. I följande exempel är avdelningsenheten uppdelad i två tabeller: Avdelning och Avdelningens Detaljer. Entitetsdelning använder flera anrop till map-metoden för att mappa en delmängd av egenskaper till en specifik tabell.

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

Mappa flera entitetstyper till en tabell i databasen (tabelldelning)

I följande exempel mappas två entitetstyper som delar en primärnyckel till en tabell.

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

Mappa en entitetstyp till Infoga/uppdatera/ta bort lagrade procedurer (EF6 och senare)

Från och med EF6 kan du mappa en entitet för att använda lagrade procedurer för att infoga uppdatering och borttagning. Mer information finns i Koda först infoga/uppdatera/ta bort lagrade procedurer.

Modell som används i exempel

Följande Code First-modell används för exemplen på den här sidan.

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