Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
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 visualizzare il modello che è possibile usare con as-is viene fornito alla fine di questo articolo.
Impostazioni Globali del 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.
Mappatura 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 è:
- La classe definisce una proprietà il cui nome è "ID" o "Id"
- 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);
Specificare la lunghezza massima di 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, riceverai un'eccezione DbEntityValidationException. Se Code First crea un database a partire da questo modello, la colonna utilizzata per memorizzare questa proprietà di solito sarà nullabile.
Annotazioni
In alcuni casi potrebbe non essere possibile per la colonna nel database essere non annullabile 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 annullabile poiché non tutti i tipi nella gerarchia dispongono di questa proprietà.
modelBuilder.Entity<Department>().Property(t => t.Name).IsRequired();
Configurazione di un indice su una o più proprietà
Annotazioni
Solo EF6.1 - 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);
Mappatura di una proprietà CLR a una colonna specifica nel database
Nell'esempio seguente viene eseguita la mappatura 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 su 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. Impostare la proprietà come versione di riga ne configura automaticamente l'uso come token di concorrenza ottimistica.
modelBuilder.Entity<OfficeAssignment>()
.Property(t => t.Timestamp)
.IsRowVersion();
Mappatura 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>();
Mappatura di un tipo di entità su 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à della tabella per tipo (TPT)
Nello scenario di mapping TPT, tutti i tipi vengono mappati a singole tabelle. Le proprietà che appartengono esclusivamente a un tipo di base o a un tipo derivato vengono archiviate in una tabella mappata a tale 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à della classe table-Per-Concrete
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.
Annotazioni
Si noti che poiché le tabelle che partecipano alla gerarchia di ereditarietà TPC non condividono una chiave primaria, potrebbero esserci chiavi di entità duplicate quando si inseriscono dati nelle tabelle mappate alle sottoclassi, se si dispone di valori generati dal database con lo stesso seed di identificazione. 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");
});
Mappatura delle proprietà di un tipo di entità su più tabelle nel database (suddivisione delle 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 mappare 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");
});
Mappatura di più tipi di entità a una tabella nel database (divisione della 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à alle stored procedure per l'inserimento, aggiornamento ed 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; }
}