Share via


API Fluent - Configurazione e mapping di proprietà e tipi

Quando si usa Entity Framework Code First, il comportamento predefinito consiste nel eseguire il mapping delle classi POCO alle tabelle usando un set di convenzioni inserite in Entity Framework. In alcuni casi, tuttavia, non è possibile o non si vuole seguire tali convenzioni ed è necessario eseguire il mapping delle entità a qualcosa di diverso da quello che dettano le convenzioni.

Esistono due modi principali per configurare Entity Framework per l'uso di elementi diversi dalle convenzioni, ad esempio annotazioni o API Fluent di EFS. Le annotazioni coprono solo un subset della funzionalità dell'API Fluent, quindi esistono scenari di mapping che non possono essere ottenuti usando le annotazioni. Questo articolo è progettato per illustrare come usare l'API Fluent per configurare le proprietà.

L'API Fluent code first è più comunemente accessibile eseguendo l'override del metodo OnModelCreating nel dbContext derivato. Gli esempi seguenti sono progettati per illustrare come eseguire varie attività con l'API Fluent e consentire di copiare il codice e personalizzarlo in base al modello, se si vuole vedere il modello con cui possono essere usate così com'è, viene fornito alla fine di questo articolo.

Impostazioni a livello di modello

Schema predefinito (EF6 e versioni successive)

A partire da EF6 è possibile usare il metodo HasDefaultSchema in DbModelBuilder per specificare lo schema del database da usare per tutte le tabelle, le stored procedure e così via. Questa impostazione predefinita verrà sostituita per tutti gli oggetti per cui si configura in modo esplicito uno schema diverso.

modelBuilder.HasDefaultSchema("sales");

Convenzioni personalizzate (EF6 e versioni successive)

A partire da EF6 è possibile creare convenzioni personalizzate per integrare quelle incluse in Code First. Per altre informazioni, vedere Convenzioni Code First personalizzate.

Mapping delle proprietà

Il metodo Property viene usato per configurare gli attributi per ogni proprietà appartenente a un'entità o a un tipo complesso. Il metodo Property viene utilizzato per ottenere un oggetto di configurazione per una determinata proprietà. Le opzioni nell'oggetto di configurazione sono specifiche del tipo configurato; IsUnicode è disponibile solo per le proprietà stringa, ad esempio.

Configurazione di una chiave primaria

La convenzione di Entity Framework per le chiavi primarie è:

  1. La classe definisce una proprietà il cui nome è "ID" o "Id"
  2. o un nome di classe seguito da "ID" o "Id"

Per impostare in modo esplicito una proprietà come chiave primaria, è possibile usare il metodo HasKey. Nell'esempio seguente viene usato il metodo HasKey per configurare la chiave primaria InstructorID nel tipo OfficeAssignment.

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

Configurazione di una chiave primaria composita

Nell'esempio seguente vengono configurate le proprietà DepartmentID e Name come chiave primaria composita del tipo Department.

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

Disattivazione dell'identità per le chiavi primarie numeriche

Nell'esempio seguente la proprietà DepartmentID viene impostata su System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None per indicare che il valore non verrà generato dal database.

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

Specifica della lunghezza massima in una proprietà

Nell'esempio seguente la proprietà Name non deve contenere più di 50 caratteri. Se si imposta un valore più lungo di 50 caratteri, si otterrà un'eccezione DbEntityValidationException . Se Code First crea un database da questo modello, imposta anche la lunghezza massima della colonna Name su 50 caratteri.

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

Configurazione della proprietà in modo che sia obbligatoria

Nell'esempio seguente è necessaria la proprietà Name. Se non si specifica name, si otterrà un'eccezione DbEntityValidationException. Se Code First crea un database da questo modello, la colonna usata per archiviare questa proprietà in genere non sarà nullable.

Nota

In alcuni casi potrebbe non essere possibile che la colonna nel database sia non nullable anche se la proprietà è obbligatoria. Ad esempio, quando si usano dati di strategia di ereditarietà TPH per più tipi vengono archiviati in una singola tabella. Se un tipo derivato include una proprietà obbligatoria, la colonna non può essere resa non nullable perché non tutti i tipi nella gerarchia avranno questa proprietà.

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

Configurazione di un indice in una o più proprietà

Nota

EF6.1 Only - L'attributo Index è stato introdotto in Entity Framework 6.1. Se si usa una versione precedente, le informazioni contenute in questa sezione non si applicano.

La creazione di indici non è supportata in modo nativo dall'API Fluent, ma è possibile usare il supporto per IndexAttribute tramite l'API Fluent. Gli attributi di indice vengono elaborati includendo un'annotazione del modello sul modello che viene quindi trasformata in un indice nel database più avanti nella pipeline. È possibile aggiungere manualmente queste stesse annotazioni usando l'API Fluent.

Il modo più semplice per eseguire questa operazione consiste nel creare un'istanza di IndexAttribute che contiene tutte le impostazioni per il nuovo indice. È quindi possibile creare un'istanza di IndexAnnotation , ovvero un tipo specifico di Entity Framework che converte le impostazioni di IndexAttribute in un'annotazione del modello che può essere archiviata nel modello di Entity Framework. Questi possono quindi essere passati al metodo HasColumnAnnotation nell'API Fluent, specificando il nome Index per l'annotazione.

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

Per un elenco completo delle impostazioni disponibili in IndexAttribute, vedere la sezione Indice delle annotazioni dei dati Code First. Ciò include la personalizzazione del nome dell'indice, la creazione di indici univoci e la creazione di indici a più colonne.

È possibile specificare più annotazioni di indice su una singola proprietà passando una matrice di IndexAttribute al costruttore di IndexAnnotation.

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

Specifica di non eseguire il mapping di una proprietà CLR a una colonna nel database

Nell'esempio seguente viene illustrato come specificare che una proprietà di un tipo CLR non è mappata a una colonna nel database.

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

Mapping di una proprietà CLR a una colonna specifica nel database

Nell'esempio seguente viene eseguito il mapping della proprietà CLR Name alla colonna di database DepartmentName.

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

Ridenominazione di una chiave esterna non definita nel modello

Se si sceglie di non definire una chiave esterna in un tipo CLR, ma si vuole specificare il nome che deve avere nel database, eseguire le operazioni seguenti:

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

Configurazione di un valore che indica se una proprietà string supporta il contenuto Unicode

Per impostazione predefinita, le stringhe sono Unicode (nvarchar in SQL Server). È possibile usare il metodo IsUnicode per specificare che una stringa deve essere di tipo varchar.

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

Configurazione del tipo di dati di una colonna di database

Il metodo HasColumnType consente il mapping a rappresentazioni diverse dello stesso tipo di base. L'uso di questo metodo non consente di eseguire alcuna conversione dei dati in fase di esecuzione. Si noti che IsUnicode è il modo preferito per impostare le colonne su varchar, perché è indipendente dal database.

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

Configurazione delle proprietà in un tipo complesso

Esistono due modi per configurare le proprietà scalari in un tipo complesso.

È possibile chiamare Property in ComplexTypeConfiguration.

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

È anche possibile usare la notazione del punto per accedere a una proprietà di un tipo complesso.

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

Configurazione di una proprietà da usare come token di concorrenza ottimistica

Per specificare che una proprietà in un'entità rappresenta un token di concorrenza, è possibile usare l'attributo ConcurrencyCheck o il metodo IsConcurrencyToken.

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

È anche possibile utilizzare il metodo IsRowVersion per configurare la proprietà in modo che sia una versione di riga nel database. Se si imposta la proprietà su una versione di riga, questa viene configurata automaticamente come token di concorrenza ottimistica.

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

Mapping dei tipi

Specifica che una classe è un tipo complesso

Per convenzione, un tipo che non ha una chiave primaria specificata viene considerato come un tipo complesso. Esistono alcuni scenari in cui Code First non rileverà un tipo complesso, ad esempio se si dispone di una proprietà denominata ID, ma non si intende che sia una chiave primaria. In questi casi, è possibile usare l'API Fluent per specificare in modo esplicito che un tipo è un tipo complesso.

modelBuilder.ComplexType<Details>();

Specifica di non eseguire il mapping di un tipo di entità CLR a una tabella nel database

Nell'esempio seguente viene illustrato come escludere il mapping di un tipo CLR a una tabella nel database.

modelBuilder.Ignore<OnlineCourse>();

Mapping di un tipo di entità a una tabella specifica nel database

Tutte le proprietà di Department verranno mappate alle colonne di una tabella denominata t_ Department.

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

È anche possibile specificare il nome dello schema nel modo seguente:

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

Mapping dell'ereditarietà TPH (Table-Per-Hierarchy)

Nello scenario di mapping TPH tutti i tipi di una gerarchia di ereditarietà vengono mappati a una singola tabella. Viene utilizzata una colonna discriminatoria per identificare il tipo di ogni riga. Quando si crea il modello con Code First, TPH è la strategia predefinita per i tipi che partecipano alla gerarchia di ereditarietà. Per impostazione predefinita, la colonna discriminatoria viene aggiunta alla tabella con il nome "Discriminator" e il nome del tipo CLR di ogni tipo nella gerarchia viene usato per i valori discriminatori. È possibile modificare il comportamento predefinito usando l'API Fluent.

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

Mapping dell'ereditarietà TPT (Table-Per-Type)

Nello scenario di mapping TPT, tutti i tipi vengono mappati a singole tabelle. Le proprietà che appartengono esclusivamente a un tipo di base o derivato sono archiviate in una tabella che viene mappata a quel tipo. Le tabelle mappate ai tipi derivati archiviano anche una chiave esterna che unisce la tabella derivata alla tabella di base.

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

Mapping dell'ereditarietà TPC (Table-Per-Concrete Class)

Nello scenario di mapping TPC tutti i tipi non astratti nella gerarchia vengono mappati a singole tabelle. Le tabelle mappate alle classi derivate non hanno alcuna relazione con la tabella mappata alla classe di base nel database. Tutte le proprietà di una classe, incluse le proprietà ereditate, vengono mappate alle colonne della tabella corrispondente.

Chiamare il metodo MapInheritedProperties per configurare ogni tipo derivato. MapInheritedProperties esegue il mapping di tutte le proprietà ereditate dalla classe di base alle nuove colonne della tabella per la classe derivata.

Nota

Si noti che poiché le tabelle che fanno parte della gerarchia di ereditarietà TPC non condividono una chiave primaria, durante l'inserimento di chiavi di entità duplicate nelle tabelle mappate alle sottoclassi se sono presenti valori generati dal database con lo stesso valore di inizializzazione identity. Per risolvere questo problema, è possibile specificare un valore di inizializzazione diverso per ogni tabella o disattivare l'identità nella proprietà della chiave primaria. Identity è il valore predefinito per le proprietà della chiave integer quando si usa 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");
});

Mapping delle proprietà di un tipo di entità a più tabelle nel database (suddivisione entità)

La suddivisione delle entità consente di distribuire le proprietà di un tipo di entità in più tabelle. Nell'esempio seguente l'entità Department viene suddivisa in due tabelle: Department e DepartmentDetails. La suddivisione delle entità usa più chiamate al metodo Map per eseguire il mapping di un subset di proprietà a una tabella specifica.

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

Mapping di più tipi di entità a una tabella nel database (suddivisione tabella)

Nell'esempio seguente vengono mappati due tipi di entità che condividono una chiave primaria a una tabella.

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

Mapping di un tipo di entità a stored procedure di inserimento/aggiornamento/eliminazione (EF6 e versioni successive)

A partire da EF6 è possibile eseguire il mapping di un'entità per usare stored procedure per l'inserimento di aggiornamenti ed eliminazioni. Per altri dettagli, vedere Code First Insert/Update/Delete Stored procedure.

Modello usato negli esempi

Il modello Code First seguente viene usato per gli esempi in questa pagina.

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