Entity Framework - Guida rapida
Cos'è Entity Framework?
Entity Framework è stato rilasciato per la prima volta nel 2008, il principale mezzo di interazione di Microsoft tra le applicazioni .NET e i database relazionali. Entity Framework è un ORM (Object Relational Mapper) che è un tipo di strumento che semplifica il mapping tra gli oggetti nel software alle tabelle e alle colonne di un database relazionale.
Entity Framework (EF) è un framework ORM open source per ADO.NET che fa parte di .NET Framework.
Un ORM si occupa di creare connessioni al database ed eseguire comandi, oltre a prendere i risultati delle query e materializzare automaticamente quei risultati come oggetti dell'applicazione.
Un ORM aiuta anche a tenere traccia delle modifiche a quegli oggetti e, quando richiesto, le manterrà anche nel database.
Perché Entity Framework?
Entity Framework è un ORM e gli ORM hanno lo scopo di aumentare la produttività dello sviluppatore riducendo l'attività ridondante di persistenza dei dati utilizzati nelle applicazioni.
Entity Framework può generare i comandi di database necessari per leggere o scrivere dati nel database ed eseguirli per te.
Se stai eseguendo query, puoi esprimere le tue query sugli oggetti del tuo dominio usando LINQ to Entity.
Entity Framework eseguirà la query pertinente nel database e quindi materializzerà i risultati in istanze degli oggetti di dominio affinché tu possa lavorare all'interno della tua app.
Ci sono altri ORM sul mercato come NHibernate e LLBLGen Pro. La maggior parte degli ORM in genere associa i tipi di dominio direttamente allo schema del database.
Entity Framework ha un livello di mapping più granulare in modo da poter personalizzare i mapping, ad esempio, mappando la singola entità a più tabelle di database o anche più entità a una singola tabella.
Entity Framework è la tecnologia di accesso ai dati consigliata da Microsoft per le nuove applicazioni.
ADO.NET sembra riferirsi direttamente alla tecnologia per set di dati e tabelle di dati.
Entity Framework è il luogo in cui vengono effettuati tutti gli investimenti in avanti, cosa che accade già da diversi anni.
Microsoft consiglia di utilizzare Entity Framework su ADO.NET o LINQ to SQL per tutti i nuovi sviluppi.
Modello concettuale
Per gli sviluppatori che sono abituati allo sviluppo incentrato sul database, il cambiamento più grande con Entity Framework è che ti consente di concentrarti sul tuo dominio aziendale. Che cosa vuoi che la tua applicazione faccia senza essere limitato da ciò che il database è in grado di fare?
Con Entity Framework, il punto focale viene definito modello concettuale. È un modello degli oggetti nella tua applicazione, non un modello del database che utilizzi per rendere persistenti i dati dell'applicazione.
Può capitare che il tuo modello concettuale si allinei con lo schema del tuo database o potrebbe essere molto diverso.
Puoi utilizzare un Visual Designer per definire il tuo modello concettuale, che può quindi generare le classi che utilizzerai alla fine nella tua applicazione.
Puoi semplicemente definire le tue classi e usare una funzionalità di Entity Framework chiamata Code First. Quindi Entity Framework comprenderà il modello concettuale.
In entrambi i casi, Entity Framework risolve come passare dal modello concettuale al database. È quindi possibile eseguire query sugli oggetti del modello concettuale e lavorare direttamente con essi.
Caratteristiche
Di seguito sono riportate le funzionalità di base di Entity Framework. Questo elenco viene creato in base alle funzionalità più importanti e anche dalle domande frequenti su Entity Framework.
- Entity Framework è uno strumento Microsoft.
- Entity Framework viene sviluppato come prodotto Open Source.
- Entity Framework non è più legato o dipendente dal ciclo di rilascio di .NET.
- Funziona con qualsiasi database relazionale con un provider Entity Framework valido.
- Generazione di comandi SQL da LINQ a Entities.
- Entity Framework creerà query con parametri.
- Tiene traccia delle modifiche agli oggetti in memoria.
- Permette di inserire, aggiornare e cancellare la generazione dei comandi.
- Funziona con un modello visivo o con le tue classi.
- Entity Framework ha il supporto per le procedure memorizzate.
L'architettura di Entity Framework, dal basso verso l'alto, è costituita da quanto segue:
Fornitori di dati
Si tratta di provider specifici dell'origine, che astraggono le interfacce ADO.NET per connettersi al database durante la programmazione in base allo schema concettuale.
Traduce i linguaggi SQL comuni come LINQ tramite l'albero dei comandi in espressioni SQL native e lo esegue sul sistema DBMS specifico.
Entità cliente
Questo livello espone il livello dell'entità al livello superiore. Entity client offre agli sviluppatori la possibilità di lavorare con entità sotto forma di righe e colonne utilizzando query SQL di entità senza la necessità di generare classi per rappresentare lo schema concettuale. Entity Client mostra i livelli del framework dell'entità, che sono le funzionalità principali. Questi livelli sono chiamati Entity Data Model.
Il Storage Layer contiene l'intero schema del database in formato XML.
Il Entity Layer che è anche un file XML definisce le entità e le relazioni.
Il Mapping layer è un file XML che mappa le entità e le relazioni definite a livello concettuale con le relazioni e le tabelle effettive definite a livello logico.
Il Metadata services che è anche rappresentato in Entity Client fornisce API centralizzate per accedere ai metadati archiviati Entity, Mapping e Storage.
Servizio oggetti
Il livello Object Services è il contesto dell'oggetto, che rappresenta la sessione di interazione tra le applicazioni e l'origine dati.
L'utilizzo principale del contesto oggetto è eseguire diverse operazioni come aggiungere, eliminare istanze di entità e salvare lo stato modificato nel database con l'aiuto di query.
È il livello ORM di Entity Framework, che rappresenta il risultato dei dati per le istanze dell'oggetto delle entità.
Questi servizi consentono agli sviluppatori di utilizzare alcune delle ricche funzionalità ORM come la mappatura della chiave primaria, il rilevamento delle modifiche e così via scrivendo query utilizzando LINQ ed Entity SQL.
Cosa c'è di nuovo in Entity Framework 6?
Framework ha un'API complessa che ti consente di avere un controllo granulare su tutto, dalla sua modellazione al suo comportamento in runtime. Parte di Entity Framework 5 risiede all'interno di .NET. Un'altra parte di essa risiede all'interno di un assembly aggiuntivo distribuito tramite NuGet.
La funzionalità principale di Entity Framework è incorporata in .NET Framework.
Il supporto Code First, che è ciò che consente a Entity Framework di usare le classi al posto di un modello visivo, e un'API più leggera per interagire con EF si trovano nel pacchetto NuGet.
Il nucleo è ciò che fornisce le query, il rilevamento delle modifiche e tutta la trasformazione dalle query alle query SQL, nonché dal ritorno dei dati agli oggetti.
Puoi usare il pacchetto NuGet EF 5 sia con .NET 4 che con .NET 4.5.
Un grande punto di confusione: .NET 4.5 ha aggiunto il supporto per enumerazioni e dati spaziali alle API di Entity Framework principali, il che significa che se usi EF 5 con .NET 4, non otterrai queste nuove funzionalità. Li otterrai solo combinando EF5 con .NET 4.5.
Diamo ora un'occhiata a Entity Framework 6. Le API di base che erano all'interno di .NET in Entity Framework 6 ora fanno parte del pacchetto NuGet.
Significa -
Tutto Entity Framework risiede all'interno di questo assembly distribuito da NuGet
Non dipenderai da .NET per fornire funzionalità specifiche come il supporto dell'enumerazione di Entity Framework e il supporto dei dati speciali.
Vedrai che una delle caratteristiche di EF6 è che supporta enumerazioni e dati spaziali per .NET 4
Per iniziare a lavorare su Entity Framework è necessario installare i seguenti strumenti di sviluppo:
- Visual Studio 2013 o versioni successive
- SQL Server 2012 o versioni successive
- Aggiornamenti di Entity Framework dal pacchetto NuGet
Microsoft fornisce una versione gratuita di visual studio che contiene anche SQL Server e può essere scaricata da www.visualstudio.com .
Installazione
Step 1- Una volta completato il download, esegui il programma di installazione. Verrà visualizzata la seguente finestra di dialogo.
Step 2 - Fare clic sul pulsante Installa e inizierà il processo di installazione.
Step 3- Una volta completato con successo il processo di installazione, vedrai la seguente finestra di dialogo. Chiudi questa finestra di dialogo e riavvia il computer se necessario.
Step 4- Apri Visual Studio dal menu di avvio che aprirà la seguente finestra di dialogo. Ci vorrà del tempo per la prima volta per la preparazione.
Step 5 - Una volta fatto tutto, vedrai la finestra principale di Visual Studio.
Creiamo un nuovo progetto da File → Nuovo → Progetto
Step 1 - Seleziona Applicazione console e fai clic sul pulsante OK.
Step 2 - In Esplora soluzioni, fare clic con il pulsante destro del mouse sul progetto.
Step 3 - Selezionare Gestisci pacchetti NuGet come mostrato nell'immagine sopra, che aprirà la finestra seguente in Visual Studio.
Step 4 - Cerca Entity Framework e installa l'ultima versione premendo il pulsante di installazione.
Step 5- Fare clic su Ok. Al termine dell'installazione, vedrai il seguente messaggio nella finestra di output.
Ora sei pronto per avviare la tua applicazione.
In questo tutorial utilizzeremo un semplice database universitario. Un database universitario può essere molto più complesso nel suo insieme, ma per scopi dimostrativi e di apprendimento, stiamo usando la forma più semplice di questo database. Il diagramma seguente contiene tre tabelle.
- Student
- Course
- Enrollment
Ogni volta che viene utilizzato un database di termini, una cosa viene direttamente alla nostra mente e questo è un diverso tipo di tabelle che ha una sorta di relazione. Esistono tre tipi di relazioni tra tabelle e la relazione tra tabelle diverse dipende da come vengono definite le colonne correlate.
- Rapporto uno-a-molti
- Relazione molti-a-molti
- Rapporto uno a uno
Rapporto uno-a-molti
La relazione uno-a-molti è il tipo di relazione più comune. In questo tipo di relazione, una riga nella tabella A può avere molte righe corrispondenti nella tabella B, ma una riga nella tabella B può avere solo una riga corrispondente nella tabella A. Ad esempio, nel diagramma sopra, la tabella Studente e Iscrizione ne ha una -to-many, ogni studente può avere più iscrizioni, ma ciascuna iscrizione appartiene a un solo studente.
Relazione molti-a-molti
In una relazione molti a molti, una riga nella tabella A può avere molte righe corrispondenti nella tabella B e viceversa. Si crea tale relazione definendo una terza tabella, chiamata tabella di giunzione, la cui chiave primaria è costituita dalle chiavi esterne sia dalla tabella A che dalla tabella B. Ad esempio, la tabella Studente e Corso hanno una relazione molti-a-molti definita da una relazione uno-a-molti da ciascuna di queste tabelle alla tabella di registrazione.
Rapporto uno a uno
Nella relazione uno a uno, una riga nella tabella A non può avere più di una riga corrispondente nella tabella B e viceversa. Viene creata una relazione uno-a-uno se entrambe le colonne correlate sono chiavi primarie o hanno vincoli univoci.
Questo tipo di relazione non è comune perché la maggior parte delle informazioni correlate in questo modo sarebbero tabelle all-in-one. Potresti usare una relazione uno-a-uno per -
- Dividi una tabella con molte colonne.
- Isolare parte di una tabella per motivi di sicurezza.
- Memorizza dati di breve durata e possono essere facilmente eliminati semplicemente eliminando la tabella.
- Memorizza le informazioni che si applicano solo a un sottoinsieme della tabella principale.
Entity Data Model (EDM) è una versione estesa del modello Entity-Relationship che specifica il modello concettuale dei dati utilizzando varie tecniche di modellazione. Si riferisce anche a una serie di concetti che descrivono la struttura dei dati, indipendentemente dalla sua forma memorizzata.
EDM supporta una serie di tipi di dati primitivi che definiscono le proprietà in un modello concettuale. Dobbiamo considerare 3 parti fondamentali che costituiscono la base per Entity Framework e collettivamente è noto come Entity Data Model. Di seguito sono riportate le tre parti fondamentali di EDM.
- Il modello dello schema di archiviazione
- Il modello concettuale
- Il modello di mappatura
Il modello dello schema di archiviazione
Il modello di archiviazione, chiamato anche SSDL (Storage Schema Definition Layer), rappresenta la rappresentazione schematica dell'archivio dati back-end.
Il modello concettuale
Il modello concettuale chiamato anche CSDL (Conceptual Schema Definition Layer) è il modello di entità reale, rispetto al quale scriviamo le nostre query.
Il modello di mappatura
Mapping Layer è solo una mappatura tra il modello concettuale e il modello di archiviazione.
Lo schema logico e la sua associazione con lo schema fisico sono rappresentati come EDM.
Visual Studio fornisce anche Entity Designer, per la creazione visiva dell'EDM e della specifica di mapping.
L'output dello strumento è il file XML (* .edmx) che specifica lo schema e la mappatura.
Il file Edmx contiene artefatti di metadati di Entity Framework.
Linguaggio di definizione dello schema
ADO.NET Entity Framework utilizza un linguaggio di definizione dei dati basato su XML chiamato Schema Definition Language (SDL) per definire lo schema EDM.
SDL definisce i tipi semplici simili ad altri tipi primitivi, tra cui String, Int32, Double, Decimal e DateTime, tra gli altri.
Anche un'enumerazione, che definisce una mappa di valori e nomi primitivi, è considerata un tipo semplice.
Le enumerazioni sono supportate solo dalla versione del framework 5.0 in poi.
I tipi complessi vengono creati da un'aggregazione di altri tipi. Una raccolta di proprietà di questi tipi definisce un tipo di entità.
Il modello di dati ha principalmente tre concetti chiave per descrivere la struttura dei dati:
- Tipo di entità
- Tipo di associazione
- Property
Tipo di entità
Il tipo di entità è l'elemento costitutivo fondamentale per descrivere la struttura dei dati in EDM.
In un modello concettuale, i tipi di entità sono costruiti dalle proprietà e descrivono la struttura dei concetti di primo livello, come Studenti e Iscrizioni in un'applicazione aziendale.
Un'entità rappresenta un oggetto specifico come uno studente o un'iscrizione specifici.
Ogni entità deve avere una chiave di entità univoca all'interno di un set di entità. Un set di entità è una raccolta di istanze di un tipo di entità specifico. I set di entità (e i set di associazioni) sono raggruppati logicamente in un contenitore di entità.
L'ereditarietà è supportata con i tipi di entità, ovvero un tipo di entità può essere derivato da un altro.
Tipo di associazione
È un altro elemento fondamentale per descrivere le relazioni in EDM. In un modello concettuale, un'associazione rappresenta una relazione tra due tipi di entità come Student e Enrollment.
Ogni associazione ha due estremità di associazione che specificano i tipi di entità coinvolti nell'associazione.
Ogni fine associazione specifica anche una molteplicità di fine associazione che indica il numero di entità che possono trovarsi a quell'estremità dell'associazione.
Una molteplicità di fine associazione può avere un valore di uno (1), zero o uno (0..1) o molti (*).
È possibile accedere alle entità a un'estremità di un'associazione tramite le proprietà di navigazione o tramite chiavi esterne se sono esposte su un tipo di entità.
Proprietà
I tipi di entità contengono proprietà che ne definiscono la struttura e le caratteristiche. Ad esempio, un tipo di entità Studente può avere proprietà come ID studente, nome ecc.
Una proprietà può contenere dati primitivi (come una stringa, un numero intero o un valore booleano) o dati strutturati (come un tipo complesso).
Entity Framework consente di eseguire query, inserire, aggiornare ed eliminare dati, utilizzando oggetti Common Language Runtime (CLR) noti come entità. Entity Framework mappa le entità e le relazioni definite nel modello in un database. Fornisce inoltre servizi per:
- Materializza i dati restituiti dal database come oggetti entità
- Tenere traccia delle modifiche apportate agli oggetti
- Gestisci la concorrenza
- Propaga le modifiche degli oggetti al database
- Associa oggetti ai controlli
La classe primaria responsabile dell'interazione con i dati come oggetti è System.Data.Entity.DbContext. L'API DbContext non viene rilasciata come parte di .NET Framework. Per essere più flessibile e frequente con il rilascio di nuove funzionalità a Code First e all'API DbContext, il team di Entity Framework distribuisce EntityFramework.dll tramite la funzionalità di distribuzione NuGet di Microsoft.
NuGet ti consente di aggiungere riferimenti ai tuoi progetti .NET estraendo le DLL pertinenti direttamente nel tuo progetto dal Web.
Un'estensione di Visual Studio denominata Library Package Manager fornisce un modo semplice per estrarre l'assembly appropriato dal Web nei progetti.
L'API DbContext è principalmente mirata a semplificare l'interazione con Entity Framework.
Riduce inoltre il numero di metodi e proprietà necessari per accedere alle attività di uso comune.
Nelle versioni precedenti di Entity Framework, queste attività erano spesso complicate da individuare e codificare.
La classe di contesto gestisce gli oggetti entità durante il runtime, che include il popolamento di oggetti con i dati da un database, il rilevamento delle modifiche e la persistenza dei dati nel database.
Definizione di una classe derivata da DbContext
Il modo consigliato per lavorare con il contesto è definire una classe che deriva da DbContext ed espone le proprietà DbSet che rappresentano le raccolte delle entità specificate nel contesto. Se stai lavorando con EF Designer, il contesto verrà generato per te. Se lavori con Code First, in genere scrivi tu stesso il contesto.
Il codice seguente è un semplice esempio che mostra che UniContext è derivato da DbContext.
È possibile utilizzare proprietà automatiche con DbSet come getter e setter.
Rende anche un codice molto più pulito, ma non è necessario utilizzarlo allo scopo di creare un DbSet quando non si dispone di altra logica da applicare.
public class UniContext : DbContext {
public UniContext() : base("UniContext") { }
public DbSet<Student> Students { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Course> Courses { get; set; }
}
In precedenza, EDM veniva utilizzato per generare classi di contesto derivate dalla classe ObjectContext.
Lavorare con ObjectContext era un po 'complesso.
DbContext è un wrapper attorno a ObjectContext che è in realtà simile a ObjectContext ed è utile e facile in tutti i modelli di sviluppo come Code First, Model First e Database First.
Interrogazioni
Esistono tre tipi di query che puoi utilizzare, ad esempio:
- Aggiunta di una nuova entità.
- Modifica o aggiornamento dei valori delle proprietà di un'entità esistente.
- Eliminazione di un'entità esistente.
Aggiunta di nuove entità
Aggiungere un nuovo oggetto con Entity Framework è semplice come costruire una nuova istanza del tuo oggetto e registrarlo usando il metodo Add su DbSet. Il codice seguente serve per aggiungere un nuovo studente al database.
private static void AddStudent() {
using (var context = new UniContext()) {
var student = new Student {
LastName = "Khan",
FirstMidName = "Ali",
EnrollmentDate = DateTime.Parse("2005-09-01")
};
context.Students.Add(student);
context.SaveChanges();
}
}
Modifica delle entità esistenti
Modificare gli oggetti esistenti è semplice come aggiornare il valore assegnato alla proprietà che si desidera modificare e chiamare SaveChanges. Nel codice seguente, il cognome di Ali è stato cambiato da Khan ad Aslam.
private static void AddStudent() {
private static void ChangeStudent() {
using (var context = new UniContext()) {
var student = (from d in context.Students
where d.FirstMidName == "Ali" select d).Single();
student.LastName = "Aslam";
context.SaveChanges();
}
}
}
Eliminazione di entità esistenti
Per eliminare un'entità utilizzando Entity Framework, usa il metodo Remove su DbSet. Rimuovi opere sia per le entità esistenti che per quelle appena aggiunte. La chiamata di Rimuovi su un'entità che è stata aggiunta ma non ancora salvata nel database annullerà l'aggiunta dell'entità. L'entità viene rimossa dal rilevamento delle modifiche e non viene più rilevata da DbContext. La chiamata di Remove su un'entità esistente di cui si tiene traccia delle modifiche registrerà l'entità per l'eliminazione la prossima volta che viene chiamato SaveChanges. L'esempio seguente mostra un'istanza in cui lo studente viene rimosso dal database il cui nome è Ali.
private static void DeleteStudent() {
using (var context = new UniContext()) {
var bay = (from d in context.Students where d.FirstMidName == "Ali" select d).Single();
context.Students.Remove(bay);
context.SaveChanges();
}
}
In Entity Framework, esistono due tipi di entità che consentono agli sviluppatori di utilizzare le proprie classi di dati personalizzate insieme al modello di dati senza apportare modifiche alle classi di dati stesse.
- Entità POCO
- Proxy dinamico
Entità POCO
POCO sta per oggetti CLR "semplici" che possono essere utilizzati come oggetti di dominio esistenti con il modello di dati.
Le classi di dati POCO che sono mappate alle entità sono definite in un modello di dati.
Supporta inoltre la maggior parte degli stessi comportamenti di query, inserimento, aggiornamento ed eliminazione dei tipi di entità generati dagli strumenti Entity Data Model.
È possibile utilizzare il modello POCO per generare tipi di entità che ignorano la persistenza da un modello concettuale.
Diamo un'occhiata al seguente esempio di Conceptual Entity Data Model.
Per generare entità POCO per il modello di entità precedente:
Step 1- Fare clic con il tasto destro sulla finestra del designer. Verrà visualizzata la seguente finestra di dialogo.
Step 2 - Seleziona Aggiungi elemento generazione codice ...
Step 3 - Selezionare EF 6.x DbContext Generator, scrivere il nome e quindi fare clic sul pulsante Aggiungi.
Vedrai in Esplora soluzioni che vengono generati i modelli POCODemo.Context.tt e POCODemo.tt.
Il POCODemo.Context genera il DbContext e gli insiemi di oggetti che puoi restituire e utilizzare per le query, ad esempio per il contesto, Studenti e Corsi, ecc.
L'altro modello si occupa di tutti i tipi Studente, Corsi, ecc. Di seguito è riportato il codice per la classe Studente che viene generato automaticamente dal Modello di entità.
namespace ConsoleApplication1 {
using System;
using System.Collections.Generic;
public partial class Student {
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
"CA2214:DoNotCallOverridableMethodsInConstructors")]
public Student() {
this.Enrollments = new HashSet<Enrollment>();
}
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public System.DateTime EnrollmentDate { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Classi simili vengono generate per le tabelle di corso e di iscrizione dal modello di entità.
Proxy dinamico
Quando si creano istanze di tipi di entità POCO, Entity Framework crea spesso istanze di un tipo derivato generato dinamicamente che funge da proxy per l'entità. Si può anche dire che si tratta di classi proxy di runtime come una classe wrapper dell'entità POCO.
È possibile sovrascrivere alcune proprietà dell'entità per eseguire azioni automaticamente quando si accede alla proprietà.
Questo meccanismo viene utilizzato per supportare il caricamento lento delle relazioni e il rilevamento automatico delle modifiche.
Questa tecnica si applica anche a quei modelli creati con Code First e EF Designer.
Se si desidera che Entity Framework supporti il caricamento lento degli oggetti correlati e tenga traccia delle modifiche nelle classi POCO, le classi POCO devono soddisfare i seguenti requisiti:
La classe di dati personalizzata deve essere dichiarata con accesso pubblico.
La classe di dati personalizzata non deve essere sigillata.
La classe di dati personalizzata non deve essere astratta.
La classe di dati personalizzata deve avere un costruttore pubblico o protetto che non dispone di parametri.
Utilizzare un costruttore protetto senza parametri se si desidera utilizzare il metodo CreateObject per creare un proxy per l'entità POCO.
La chiamata al metodo CreateObject non garantisce la creazione del proxy: la classe POCO deve seguire gli altri requisiti descritti in questo argomento.
La classe non può implementare le interfacce IEntityWithChangeTracker o IEntityWithRelationships perché le classi proxy implementano queste interfacce.
L'opzione ProxyCreationEnabled deve essere impostata su true.
Il seguente esempio è di una classe entità proxy dinamico.
public partial class Course {
public Course() {
this.Enrollments = new HashSet<Enrollment>();
}
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Per disabilitare la creazione di oggetti proxy, impostare il valore della proprietà ProxyCreationEnabled su false.
Nei database relazionali, la relazione è una situazione che esiste tra le tabelle del database relazionale tramite chiavi esterne. Una chiave esterna (FK) è una colonna o una combinazione di colonne utilizzata per stabilire e applicare un collegamento tra i dati in due tabelle. Il diagramma seguente contiene tre tabelle.
- Student
- Course
- Enrollment
Nel diagramma sopra, puoi vedere una sorta di associazione / relazione tra le tabelle. Esistono tre tipi di relazioni tra tabelle e la relazione tra tabelle diverse dipende da come vengono definite le colonne correlate.
- Rapporto uno-a-molti
- Relazione molti-a-molti
- Rapporto uno a uno
Rapporto uno-a-molti
Una relazione uno-a-molti è il tipo di relazione più comune.
In questo tipo di relazione, una riga nella tabella A può avere molte righe corrispondenti nella tabella B, ma una riga nella tabella B può avere solo una riga corrispondente nella tabella A.
La chiave esterna è definita nella tabella che rappresenta le molte estremità della relazione.
Ad esempio, nel diagramma precedente le tabelle Studente e Iscrizione hanno una relazione da uno a più, ogni studente può avere molte iscrizioni, ma ciascuna iscrizione appartiene a un solo studente.
In entity framework, queste relazioni possono essere create anche con il codice. Di seguito è riportato un esempio di classi Studente e Iscrizione associate a una relazione uno a molti.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
Nel codice sopra, puoi vedere che la classe Student contiene la raccolta di Enrollment, ma la classe Enrollment ha un singolo oggetto Student.
Relazione molti-a-molti
Nella relazione molti-a-molti, una riga nella tabella A può avere molte righe corrispondenti nella tabella B e viceversa.
È possibile creare tale relazione definendo una terza tabella, chiamata tabella di giunzione, la cui chiave primaria è costituita dalle chiavi esterne sia della tabella A che della tabella B.
Ad esempio, le tabelle Studente e Corso hanno una relazione molti-a-molti definita dalla relazione uno-a-molti da ciascuna di queste tabelle alla tabella Iscrizione.
Il codice seguente contiene la classe Course e le due classi precedenti, ovvero Student e Enrollment.
public class Course {
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Puoi vedere che sia la classe Course che la classe Student hanno raccolte di oggetti Enrollment che creano una relazione molti-a-molti tramite la classe junction Enrollment.
Rapporto uno a uno
In una relazione uno a uno, una riga nella tabella A non può avere più di una riga corrispondente nella tabella B e viceversa.
Viene creata una relazione uno-a-uno se entrambe le colonne correlate sono chiavi primarie o hanno vincoli univoci.
In una relazione uno a uno, la chiave primaria funge inoltre da chiave esterna e non esiste una colonna di chiave esterna separata per nessuna delle due tabelle.
Questo tipo di relazione non è comune perché la maggior parte delle informazioni correlate in questo modo sarebbero tutte in una tabella. Potresti usare una relazione uno-a-uno per -
- Dividi una tabella con molte colonne.
- Isolare parte di una tabella per motivi di sicurezza.
- Memorizza dati di breve durata e possono essere facilmente eliminati semplicemente eliminando la tabella.
- Memorizza le informazioni che si applicano solo a un sottoinsieme della tabella principale.
Il codice seguente serve per aggiungere un altro nome di classe StudentProfile che contiene l'ID e-mail e la password dello studente.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
public virtual StudentProfile StudentProfile { get; set; }
}
public class StudentProfile {
public StudentProfile() {}
public int ID { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public virtual Student Student { get; set; }
}
Puoi vedere che la classe di entità Student contiene la proprietà di navigazione StudentProfile e StudentProfile contiene la proprietà di navigazione Student.
Ogni studente ha una sola email e password per accedere al dominio universitario. Queste informazioni possono essere aggiunte alla tabella Studente ma per motivi di sicurezza vengono separate in un'altra tabella.
Tutta la vita
La durata di un contesto inizia quando l'istanza viene creata e termina quando l'istanza viene eliminata o sottoposta a garbage collection.
La durata del contesto è una decisione cruciale da prendere quando usiamo ORM.
Il contesto si comporta come una cache di entità, quindi significa che contiene riferimenti a tutte le entità caricate che possono crescere molto velocemente nel consumo di memoria e può anche causare perdite di memoria.
Nel diagramma sottostante, puoi vedere il livello superiore del flusso di lavoro dei dati dall'applicazione al database tramite il contesto e viceversa.
Ciclo di vita dell'entità
Il ciclo di vita dell'entità descrive il processo in cui un'entità viene creata, aggiunta, modificata, eliminata, ecc. Le entità hanno molti stati durante la sua vita. Prima di esaminare come recuperare lo stato dell'entità, diamo un'occhiata a cos'è lo stato dell'entità. Lo stato è un enum di tipoSystem.Data.EntityState che dichiara i seguenti valori -
Added: L'entità è contrassegnata come aggiunta.
Deleted: L'entità è contrassegnata come eliminata.
Modified: L'entità è stata modificata.
Unchanged: L'entità non è stata modificata.
Detached: L'entità non viene tracciata.
Cambiamenti di stato nel ciclo di vita dell'entità
A volte lo stato delle entità viene impostato automaticamente dal contesto, ma può anche essere modificato manualmente dallo sviluppatore. Anche se tutte le combinazioni di passaggi da uno stato a un altro sono possibili, ma alcune di esse sono prive di significato. Per esempio,Added entità al Deleted stato o viceversa.
Parliamo di stati diversi.
Stato invariato
Quando un'entità è invariata, è vincolata al contesto ma non è stata modificata.
Per impostazione predefinita, un'entità recuperata dal database si trova in questo stato.
Quando un'entità è collegata al contesto (con il metodo Attach), allo stesso modo si trova nello stato Unchanged.
Il contesto non può tenere traccia delle modifiche agli oggetti a cui non fa riferimento, quindi quando sono collegati presuppone che siano invariati.
Stato distaccato
Staccato è lo stato predefinito di un'entità appena creata perché il contesto non può tenere traccia della creazione di alcun oggetto nel codice.
Questo è vero anche se istanziate l'entità all'interno di un blocco using del contesto.
Staccato è anche lo stato delle entità recuperate dal database quando il rilevamento è disabilitato.
Quando un'entità è scollegata, non è vincolata al contesto, quindi il suo stato non viene tracciato.
Può essere smaltito, modificato, utilizzato in combinazione con altre classi o utilizzato in qualsiasi altro modo necessario.
Poiché non è presente alcun rilevamento del contesto, non ha alcun significato per Entity Framework.
Stato aggiunto
Quando un'entità è nello stato Aggiunto, hai poche opzioni. In effetti, puoi solo staccarlo dal contesto.
Naturalmente, anche se modifichi una proprietà, lo stato rimane Aggiunto, perché spostarlo in Modificato, Non modificato o Eliminato non ha senso.
È una nuova entità e non ha corrispondenza con una riga nel database.
Questo è un prerequisito fondamentale per trovarsi in uno di quegli stati (ma questa regola non viene applicata dal contesto).
Stato modificato
Quando un'entità viene modificata, significa che era nello stato Non modificato e quindi alcune proprietà sono state modificate.
Dopo che un'entità entra nello stato Modificato, può passare allo stato Scollegato o Eliminato, ma non può tornare allo stato Non modificato anche se ripristini manualmente i valori originali.
Non può nemmeno essere modificato in Aggiunto, a meno che non si scolleghi e si aggiunga l'entità al contesto, perché una riga con questo ID esiste già nel database e si otterrebbe un'eccezione di runtime quando la si rende persistente.
Stato eliminato
Un'entità entra nello stato Deleted perché era Unchanged o Modified e quindi è stato utilizzato il metodo DeleteObject.
Questo è lo stato più restrittivo, perché è inutile passare da questo stato a qualsiasi altro valore tranne Detached.
Il usingse si desidera che tutte le risorse controllate dal contesto vengano eliminate alla fine del blocco. Quando usi l'estensioneusing istruzione, quindi il compilatore crea automaticamente un blocco try / latest e chiama dispose nel blocco infine.
using (var context = new UniContext()) {
var student = new Student {
LastName = "Khan",
FirstMidName = "Ali",
EnrollmentDate = DateTime.Parse("2005-09-01")
};
context.Students.Add(student);
context.SaveChanges();
}
Quando si lavora con un contesto di lunga durata, considerare quanto segue:
Man mano che si caricano più oggetti e relativi riferimenti in memoria, il consumo di memoria del contesto può aumentare rapidamente. Ciò potrebbe causare problemi di prestazioni.
Ricordarsi di smaltire il contesto quando non è più necessario.
Se un'eccezione fa sì che il contesto sia in uno stato irreversibile, l'intera applicazione può terminare.
Le possibilità di incorrere in problemi relativi alla concorrenza aumentano con l'aumentare del divario tra il momento in cui i dati vengono interrogati e quelli aggiornati.
Quando si lavora con le applicazioni Web, utilizzare un'istanza di contesto per richiesta.
Quando si lavora con Windows Presentation Foundation (WPF) o Windows Form, utilizzare un'istanza di contesto per modulo. Ciò consente di utilizzare la funzionalità di rilevamento delle modifiche fornita dal contesto.
Regole pratiche
Web Applications
È ormai prassi comune e migliore che per le applicazioni web, il contesto venga utilizzato per richiesta.
Nelle applicazioni web ci occupiamo di richieste molto brevi ma che trattengono tutte le transazioni del server, sono quindi della durata adeguata al contesto in cui vivere.
Desktop Applications
Per applicazioni desktop, come Win Forms / WPF, ecc., Il contesto viene utilizzato per modulo / finestra di dialogo / pagina.
Poiché non vogliamo avere il contesto come singleton per la nostra applicazione, lo elimineremo quando passeremo da una forma all'altra.
In questo modo, acquisiremo molte delle capacità del contesto e non subiremo le implicazioni di contesti di lunga durata.
Entity Framework fornisce tre approcci per creare un modello di entità e ognuno ha i propri pro e contro.
- Code First
- Database First
- Primo modello
In questo capitolo descriveremo brevemente il primo approccio al codice. Alcuni sviluppatori preferiscono lavorare con Designer in Code, mentre altri preferiscono lavorare semplicemente con il loro codice. Per questi sviluppatori, Entity Framework ha un flusso di lavoro di modellazione denominato Code First.
Il flusso di lavoro di modellazione Code First è destinato a un database che non esiste e Code First lo creerà.
Può essere utilizzato anche se si dispone di un database vuoto e quindi Code First aggiungerà anche nuove tabelle.
Code First consente di definire il modello utilizzando le classi C # o VB.Net.
Facoltativamente, è possibile eseguire una configurazione aggiuntiva utilizzando attributi nelle classi e nelle proprietà o utilizzando un'API fluente.
Perché Code First?
Code First è in realtà costituito da un insieme di pezzi del puzzle. Le prime sono le classi di dominio.
Le classi di dominio non hanno nulla a che fare con Entity Framework. Sono solo gli elementi del tuo dominio aziendale.
Entity Framework, quindi, ha un contesto che gestisce l'interazione tra quelle classi e il database.
Il contesto non è specifico di Code First. È una funzionalità di Entity Framework.
Code First aggiunge un generatore di modelli che ispeziona le classi gestite dal contesto, quindi utilizza una serie di regole o convenzioni per determinare come tali classi e relazioni descrivono un modello e come tale modello deve essere associato al database.
Tutto questo accade in fase di esecuzione. Non vedrai mai questo modello, è solo nella memoria.
Code First ha la capacità di utilizzare quel modello per creare un database, se necessario.
Può anche aggiornare il database se il modello cambia, utilizzando una funzionalità chiamata Code First Migrations.
In questo capitolo, impareremo come creare un modello di dati di entità nel designer utilizzando il flusso di lavoro denominato Model First.
Model First è ottimo per quando si avvia un nuovo progetto in cui il database non esiste ancora.
Il modello è archiviato in un file EDMX e può essere visualizzato e modificato in Entity Framework Designer.
In Model First, si definisce il modello in un designer di Entity Framework, quindi si genera SQL, che creerà lo schema del database per abbinare il modello e quindi si esegue l'SQL per creare lo schema nel database.
Le classi con cui interagisci nell'applicazione vengono generate automaticamente dal file EDMX.
Di seguito è riportato un semplice esempio di creazione di un nuovo progetto console utilizzando l'approccio Model First.
Step 1 - Apri Visual Studio e seleziona File → Nuovo → Progetto
Step 2 - Seleziona Installato → Modelli → Visual C # → Windows dal riquadro di sinistra, quindi nel riquadro centrale, seleziona Applicazione console.
Step 3 - Inserisci EFModelFirstDemo nel campo Nome.
Step 4 - Per creare il modello, fai prima clic con il pulsante destro del mouse sul progetto della console in Esplora soluzioni e seleziona Aggiungi → Nuovi elementi ...
Si aprirà la seguente finestra di dialogo.
Step 5 - Selezionare ADO.NET Entity Data Model dal riquadro centrale e immettere il nome ModelFirstDemoDB nel campo Nome.
Step 6 - Fare clic sul pulsante Aggiungi che avvierà la finestra di dialogo Creazione guidata modello di dati entità.
Step 7- Seleziona il modello EF Designer vuoto e fai clic sul pulsante Avanti. Entity Framework Designer si apre con un modello vuoto. Ora possiamo iniziare ad aggiungere entità, proprietà e associazioni al modello.
Step 8- Fare clic con il pulsante destro del mouse sulla superficie del progetto e selezionare Proprietà. Nella finestra Proprietà, modificare il nome del contenitore di entità in ModelFirstDemoDBContext.
Step 9 - Fare clic con il pulsante destro del mouse sull'area di progettazione e selezionare Aggiungi nuovo → Entità ...
La finestra di dialogo Aggiungi entità si aprirà come mostrato nell'immagine seguente.
Step 10 - Immettere Student come nome entità e Student Id come nome proprietà e fare clic su Ok.
Step 11 - Fare clic con il pulsante destro del mouse sulla nuova entità nell'area di progettazione e selezionare Aggiungi nuovo → Proprietà scalare, immettere Nome come nome della proprietà.
Step 12 - Immettere FirstName e quindi aggiungere altre due proprietà scalari come LastName e EnrollmentDate.
Step 13 - Aggiungi altre due entità Corso e Iscrizione seguendo tutti i passaggi sopra menzionati e aggiungi anche alcune proprietà scalari come mostrato nei passaggi seguenti.
Step 14 - Abbiamo tre entità in Visual Designer, aggiungiamo qualche associazione o relazione tra di loro.
Step 15 - Fare clic con il pulsante destro del mouse sull'area di progettazione e selezionare Aggiungi nuovo → Associazione ...
Step 16 - Fare in modo che un'estremità della relazione punti allo studente con una molteplicità di uno e l'altra estremità all'iscrizione con una molteplicità di molti.
Step 17 - Ciò significa che uno studente ha molte iscrizioni e l'iscrizione appartiene a uno studente.
Step 18 - Assicurati che la casella Aggiungi proprietà chiave esterna all'entità "Post" sia selezionata e fai clic su OK.
Step 19 - Allo stesso modo, aggiungi un'altra associazione tra Corso e Iscrizione.
Step 20 - Il tuo modello di dati apparirà come la schermata seguente dopo aver aggiunto associazioni tra entità.
Ora abbiamo un semplice modello da cui possiamo generare un database e utilizzarlo per leggere e scrivere dati. Andiamo avanti e generiamo il database.
Step 1 - Fare clic con il pulsante destro del mouse sull'area di progettazione e selezionare Genera database da modello ...
Step 2 - È possibile selezionare il database esistente o creare una nuova connessione facendo clic su Nuova connessione ...
Step 3 - Per creare un nuovo database, fare clic su Nuova connessione ...
Step 4 - Immettere il nome del server e il nome del database.
Step 5 - Fare clic su Avanti.
Step 6- Fare clic su Fine. Questo aggiungerà il file * .edmx.sql nel progetto. È possibile eseguire script DDL in Visual Studio aprendo il file .sql, quindi fare clic con il pulsante destro del mouse e selezionare Esegui.
Step 7 - Verrà visualizzata la seguente finestra di dialogo per la connessione al database.
Step 8 - In caso di corretta esecuzione, vedrai il seguente messaggio.
Step 9 - Vai al server explorer, vedrai che il database viene creato con tre tabelle specificate.
Successivamente, dobbiamo scambiare il nostro modello per generare codice che utilizza l'API DbContext.
Step 1 - Fai clic con il pulsante destro del mouse su un punto vuoto del tuo modello in EF Designer e seleziona Aggiungi elemento per la generazione del codice ...
Vedrai che si apre la seguente finestra di dialogo Aggiungi nuovo elemento.
Step 2 - Seleziona EF 6.x DbContext Generator nel riquadro centrale e inserisci ModelFirstDemoModel nel campo Nome.
Step 3 - Vedrai in Esplora soluzioni che vengono generati modelli ModelFirstDemoModel.Context.tt e ModelFirstDemoModel.tt.
ModelFirstDemoModel.Context genera il DbCcontext e i set di oggetti che puoi restituire e utilizzare per le query, ad esempio per il contesto, Studenti e Corsi ecc.
L'altro modello si occupa di tutti i tipi Studente, Corsi ecc. Di seguito è riportata la classe Studente, che viene generata automaticamente dal Modello di entità.
Di seguito è riportato il codice C # in cui vengono immessi e recuperati alcuni dati dal database.
using System;
using System.Linq;
namespace EFModelFirstDemo {
class Program {
static void Main(string[] args) {
using (var db = new ModelFirstDemoDBContext()) {
// Create and save a new Student
Console.Write("Enter a name for a new Student: ");
var firstName = Console.ReadLine();
var student = new Student {
StudentID = 1,
FirstName = firstName
};
db.Students.Add(student);
db.SaveChanges();
var query = from b in db.Students
orderby b.FirstName select b;
Console.WriteLine("All student in the database:");
foreach (var item in query) {
Console.WriteLine(item.FirstName);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
}
Quando il codice sopra viene eseguito, riceverai il seguente output:
Enter a name for a new Student:
Ali Khan
All student in the database:
Ali Khan
Press any key to exit...
Ti consigliamo di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
In questo capitolo, impariamo a creare un modello di dati di entità con l'approccio Database First.
L'approccio Database First fornisce un'alternativa agli approcci Code First e Model First al Entity Data Model. Crea codici modello (classi, proprietà, DbContext ecc.) Dal database nel progetto e quelle classi diventano il collegamento tra il database e il controller.
Il Database First Approach crea l'entità framework da un database esistente. Usiamo tutte le altre funzionalità, come la sincronizzazione del modello / database e la generazione del codice, nello stesso modo in cui le abbiamo utilizzate nell'approccio Model First.
Facciamo un semplice esempio. Abbiamo già un database che contiene 3 tabelle come mostrato nell'immagine seguente.
Step 1 - Creiamo un nuovo progetto console con il nome DatabaseFirstDemo.
Step 2 - Per creare il modello, prima fai clic con il pulsante destro del mouse sul progetto della console in Esplora soluzioni e seleziona Aggiungi → Nuovi elementi ...
Step 3 - Selezionare ADO.NET Entity Data Model dal riquadro centrale e immettere il nome DatabaseFirstModel nel campo Nome.
Step 4 - Fare clic sul pulsante Aggiungi che avvierà la finestra di dialogo Creazione guidata modello di dati entità.
Step 5 - Seleziona EF Designer dal database e fai clic sul pulsante Avanti.
Step 6 - Seleziona il database esistente e fai clic su Avanti.
Step 7 - Scegli Entity Framework 6.x e fai clic su Avanti.
Step 8 - Seleziona tutte le viste delle tabelle e la stored procedure che desideri includere e fai clic su Fine.
Vedrai che il modello di entità e le classi POCO vengono generate dal database.
Recuperiamo ora tutti gli studenti dal database scrivendo il seguente codice nel file program.cs.
using System;
using System.Linq;
namespace DatabaseFirstDemo {
class Program {
static void Main(string[] args) {
using (var db = new UniContextEntities()) {
var query = from b in db.Students
orderby b.FirstMidName select b;
Console.WriteLine("All All student in the database:");
foreach (var item in query) {
Console.WriteLine(item.FirstMidName +" "+ item.LastName);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
}
Quando il programma di cui sopra viene eseguito, riceverai il seguente output:
All student in the database:
Ali Khan
Arturo finand
Bill Gates
Carson Alexander
Gytis Barzdukas
Laura Norman
Meredith Alonso
Nino Olivetto
Peggy Justice
Yan Li
Press any key to exit...
Quando il programma di cui sopra viene eseguito, vedrai tutti i nomi degli studenti che erano stati precedentemente inseriti nel database.
Ti consigliamo di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
In questo capitolo, concentriamoci sulla creazione di modelli con Designer o Database First o semplicemente utilizzando Code First. Di seguito sono riportate alcune linee guida che ti aiuteranno a decidere quale flusso di lavoro di modellazione scegliere.
Abbiamo già visto esempi di modellazione Code First, modellazione Database First e workflow di modellazione Model First.
I flussi di lavoro Database First e Model First hanno utilizzato Designer, ma uno inizia con il database per creare un modello e l'altro inizia con il modello per creare un database.
Per quegli sviluppatori che non vogliono usare Visual Designer più la generazione di codice, Entity Framework ha un flusso di lavoro completamente diverso chiamato Code First.
Il flusso di lavoro tipico per Code First è ottimo per le nuove applicazioni in cui non si dispone nemmeno di un database. Definisci le tue classi e il codice e poi lascia che Code First capisca come dovrebbe apparire il tuo database.
È anche possibile avviare Code First con un database e questo rende Code First un po 'una contraddizione. Ma c'è uno strumento che ti consente di decodificare un database in classi che è un ottimo modo per ottenere un vantaggio sulla codifica.
Date queste opzioni, esaminiamo l'albero decisionale.
Se preferisci lavorare con un Visual Designer nel codice generato, ti consigliamo di scegliere uno dei flussi di lavoro che coinvolgono EF Designer. Se il tuo database esiste già, Database First è il tuo percorso.
Se vuoi usare un Visual Designer su un progetto nuovo di zecca senza un database, allora ti consigliamo di usare Model First.
Se vuoi solo lavorare con il codice e non con un designer, allora Code First è probabilmente per te insieme alla possibilità di utilizzare lo strumento che decodifica il database in classi.
Se disponi di classi esistenti, la soluzione migliore è usarle con Code First.
Nei capitoli precedenti, hai appreso tre diversi modi per definire un modello di dati di entità.
Due di questi, Database First e Model First, dipendevano dal designer di Entity Framework combinato con la generazione del codice.
Il terzo, Code First, ti consente di saltare un visual designer e scrivere semplicemente il tuo codice.
Indipendentemente dal percorso scelto, ti ritroverai con classi di dominio e una o più classi DbContext di Entity Framework ti consentono di recuperare e rendere persistenti i dati rilevanti per tali classi.
L'API DbContext nelle tue applicazioni viene utilizzata come ponte tra le tue classi e il tuo database. DbContext è una delle classi più importanti in Entity Framework.
Consente di esprimere ed eseguire query.
Prende i risultati delle query dal database e li trasforma in istanze delle nostre classi modello.
Può tenere traccia delle modifiche alle entità, incluse l'aggiunta e l'eliminazione, e quindi attiva la creazione di istruzioni di inserimento, aggiornamento ed eliminazione che vengono inviate al database su richiesta.
Di seguito sono riportate le classi di contesto dell'annuncio di dominio su cui eseguiremo diverse operazioni in questo capitolo. Questo è lo stesso esempio che abbiamo creato nel capitolo, Database First Approach.
Implementazione della classe di contesto
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Core.Objects;
using System.Linq;
namespace DatabaseFirstDemo {
public partial class UniContextEntities : DbContext {
public UniContextEntities(): base("name = UniContextEntities") {}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
}
Implementazione delle classi di dominio
Classe del corso
namespace DatabaseFirstDemo {
using System;
using System.Collections.Generic;
public partial class Course {
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
"CA2214:DoNotCallOverridableMethodsInConstructors")]
public Course() {
this.Enrollments = new HashSet<Enrollment>();
}
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
"CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Classe degli studenti
namespace DatabaseFirstDemo {
using System;
using System.Collections.Generic;
public partial class Student {
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
"CA2214:DoNotCallOverridableMethodsInConstructors")]
public Student() {
this.Enrollments = new HashSet<Enrollment>();
}
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public System.DateTime EnrollmentDate { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage",
"CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Classe di iscrizione
namespace DatabaseFirstDemo {
using System;
using System.Collections.Generic;
public partial class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Nullable<int> Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
}
Crea operazione
Aggiungere un nuovo oggetto con Entity Framework è semplice come costruire una nuova istanza del tuo oggetto e registrarlo usando il metodo Add su DbSet. Il codice seguente consente di aggiungere un nuovo studente al database.
class Program {
static void Main(string[] args) {
var newStudent = new Student();
//set student name
newStudent.FirstMidName = "Bill";
newStudent.LastName = "Gates";
newStudent.EnrollmentDate = DateTime.Parse("2015-10-21");
newStudent.ID = 100;
//create DBContext object
using (var dbCtx = new UniContextEntities()) {
//Add Student object into Students DBset
dbCtx.Students.Add(newStudent);
// call SaveChanges method to save student into database
dbCtx.SaveChanges();
}
}
}
Operazione di aggiornamento
Modificare gli oggetti esistenti è semplice come aggiornare il valore assegnato alla proprietà che si desidera modificare e chiamare SaveChanges. Ad esempio, il codice seguente viene utilizzato per modificare il cognome di Ali da Khan ad Aslam.
using (var context = new UniContextEntities()) {
var student = (from d in context.Students where d.FirstMidName == "Ali" select d).Single();
student.LastName = "Aslam";
context.SaveChanges();
}
Elimina operazione
Per eliminare un'entità utilizzando Entity Framework, usa il metodo Remove su DbSet. Rimuovi opere sia per le entità esistenti che per quelle appena aggiunte. La chiamata di Rimuovi su un'entità che è stata aggiunta ma non ancora salvata nel database annullerà l'aggiunta dell'entità. L'entità viene rimossa dal rilevamento delle modifiche e non viene più rilevata da DbContext. La chiamata di Remove su un'entità esistente di cui si tiene traccia delle modifiche registrerà l'entità per l'eliminazione la prossima volta che viene chiamato SaveChanges. L'esempio seguente è di un codice in cui lo studente viene rimosso dal database il cui nome è Ali.
using (var context = new UniContextEntities()) {
var bay = (from d in context.Students where d.FirstMidName == "Ali" select d).Single();
context.Students.Remove(bay);
context.SaveChanges();
}
Leggi operazione
Leggere i dati esistenti dal database è molto semplice. Di seguito è riportato il codice in cui vengono recuperati tutti i dati dalla tabella Studente e quindi verrà visualizzato un programma con il nome e il cognome degli studenti in ordine alfabetico.
using (var db = new UniContextEntities()) {
var query = from b in db.Students orderby b.FirstMidName select b;
Console.WriteLine("All All student in the database:");
foreach (var item in query) {
Console.WriteLine(item.FirstMidName +" "+ item.LastName);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
Qualsiasi sviluppatore di accesso ai dati incontra difficoltà nel rispondere alla domanda relativa alla concorrenza dei dati: "Cosa succede se più di una persona sta modificando gli stessi dati contemporaneamente?"
I più fortunati tra noi hanno a che fare con regole aziendali che dicono "nessun problema, vince l'ultimo in classifica".
In questo caso, la concorrenza non è un problema. Più probabilmente, non è così semplice e non esiste un proiettile d'argento per risolvere ogni scenario contemporaneamente.
Per impostazione predefinita, Entity Framework prenderà il percorso di "last one in wins", il che significa che l'ultimo aggiornamento viene applicato anche se qualcun altro ha aggiornato i dati tra l'ora in cui i dati sono stati recuperati e l'ora in cui i dati sono stati salvati.
Facciamo un esempio per capirlo meglio. L'esempio seguente aggiunge una nuova colonna VersionNo nella tabella Course.
Vai al designer e fai clic con il tasto destro sulla finestra del designer e seleziona Aggiorna modello dal database ...
Vedrai che un'altra colonna viene aggiunta in Entità corso.
Fare clic con il pulsante destro del mouse sulla colonna VersionNo appena creata e selezionare Proprietà e modificare ConcurrencyMode in Fixed come mostrato nell'immagine seguente.
Con ConcurrencyMode di Course.VersionNo impostato su Fixed, ogni volta che un corso viene aggiornato, il comando Update cercherà il corso utilizzando la sua EntityKey e la sua proprietà VersionNo.
Diamo un'occhiata a un semplice scenario. Due utenti recuperano lo stesso corso contemporaneamente e l'utente 1 cambia il titolo di quel corso in Matematica e salva le modifiche prima dell'utente 2. In seguito, quando l'utente 2 cambia il titolo di quel corso che è stato recuperato prima che l'utente 1 salvi le sue modifiche, in questo caso l'utente 2 otterrà l'eccezione di concorrenza"User2: Optimistic Concurrency exception occured".
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
namespace DatabaseFirstDemo {
class Program {
static void Main(string[] args) {
Course c1 = null;
Course c2 = null;
//User 1 gets Course
using (var context = new UniContextEntities()) {
context.Configuration.ProxyCreationEnabled = false;
c1 = context.Courses.Where(s ⇒ s.CourseID == 1).Single();
}
//User 2 also get the same Course
using (var context = new UniContextEntities()) {
context.Configuration.ProxyCreationEnabled = false;
c2 = context.Courses.Where(s ⇒ s.CourseID == 1).Single();
}
//User 1 updates Course Title
c1.Title = "Edited from user1";
//User 2 updates Course Title
c2.Title = "Edited from user2";
//User 1 saves changes first
using (var context = new UniContextEntities()) {
try {
context.Entry(c1).State = EntityState.Modified;
context.SaveChanges();
} catch (DbUpdateConcurrencyException ex) {
Console.WriteLine("User1: Optimistic Concurrency exception occurred");
}
}
//User 2 saves changes after User 1.
//User 2 will get concurrency exection
//because CreateOrModifiedDate is different in the database
using (var context = new UniContextEntities()) {
try {
context.Entry(c2).State = EntityState.Modified;
context.SaveChanges();
} catch (DbUpdateConcurrencyException ex) {
Console.WriteLine("User2: Optimistic Concurrency exception occurred");
}
}
}
}
}
In tutte le versioni di Entity Framework, ogni volta che esegui SaveChanges()per inserire, aggiornare o eliminare il database, il framework avvolgerà tale operazione in una transazione. Quando si richiama SaveChanges, il contesto avvia automaticamente una transazione e la esegue il commit o il rollback a seconda che la persistenza sia riuscita.
Tutto questo è trasparente per te e non avrai mai bisogno di affrontarlo.
Questa transazione dura solo il tempo necessario per eseguire l'operazione e quindi viene completata.
Quando si esegue un'altra operazione simile, inizia una nuova transazione.
Entity Framework 6 fornisce quanto segue:
Database.BeginTransaction ()
È un metodo semplice e più semplice all'interno di un DbContext esistente per avviare e completare le transazioni per gli utenti.
Consente di combinare più operazioni all'interno della stessa transazione e quindi viene eseguito il commit o il rollback di tutte.
Consente inoltre all'utente di specificare più facilmente il livello di isolamento per la transazione.
Database.UseTransaction ()
Consente a DbContext di utilizzare una transazione avviata all'esterno di Entity Framework.
Diamo uno sguardo al seguente esempio in cui vengono eseguite più operazioni in una singola transazione. Il codice è come -
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
using (var dbContextTransaction = context.Database.BeginTransaction()) {
try {
Student student = new Student() {
ID = 200,
FirstMidName = "Ali",
LastName = "Khan",
EnrollmentDate = DateTime.Parse("2015-12-1")
};
context.Students.Add(student);
context.Database.ExecuteSqlCommand(@"UPDATE Course SET Title =
'Calculus'" + "WHERE CourseID = 1045");
var query = context.Courses.Where(c ⇒ c.CourseID == 1045);
foreach (var item in query) {
Console.WriteLine(item.CourseID.ToString()
+ " " + item.Title + " " + item.Credits);
}
context.SaveChanges();
var query1 = context.Students.Where(s ⇒ s.ID == 200);
foreach (var item in query1) {
Console.WriteLine(item.ID.ToString()
+ " " + item.FirstMidName + " " + item.LastName);
}
dbContextTransaction.Commit();
} catch (Exception) {
dbContextTransaction.Rollback();
}
}
}
}
}
L'inizio di una transazione richiede che la connessione del negozio sottostante sia aperta.
Quindi chiamare Database.BeginTransaction () aprirà la connessione, se non è già aperta.
Se DbContextTransaction ha aperto la connessione, la chiuderà quando viene chiamato Dispose ().
Una vista è un oggetto che contiene i dati ottenuti da una query predefinita. Una vista è un oggetto virtuale o una tabella il cui set di risultati è derivato da una query. È molto simile a una tabella reale perché contiene colonne e righe di dati. Di seguito sono riportati alcuni usi tipici delle visualizzazioni:
- Filtra i dati delle tabelle sottostanti
- Filtra i dati per motivi di sicurezza
- Centralizza i dati distribuiti su più server
- Crea un set di dati riutilizzabile
Le viste possono essere utilizzate in modo simile alle tabelle. Per utilizzare la vista come entità, devi prima aggiungere le viste del database a EDM. Dopo aver aggiunto le viste al tuo modello, puoi lavorarci allo stesso modo delle entità normali tranne che per le operazioni di creazione, aggiornamento ed eliminazione.
Diamo un'occhiata a come aggiungere viste nel modello dal database.
Step 1 - Crea un nuovo progetto di applicazione console.
Step 2 - Fare clic con il pulsante destro del mouse sul progetto in Esplora soluzioni e selezionare Aggiungi → Nuovo elemento.
Step 3 - Selezionare ADO.NET Entity Data Model dal riquadro centrale e immettere il nome ViewModel nel campo Nome.
Step 4 - Fare clic sul pulsante Aggiungi che avvierà la finestra di dialogo Creazione guidata modello di dati entità.
Step 5 - Seleziona EF Designer dal database e fai clic sul pulsante Avanti.
Step 6 - Seleziona il database esistente e fai clic su Avanti.
Step 7 - Scegli Entity Framework 6.x e fai clic su Avanti.
Step 8 - Seleziona tabelle e viste dal tuo database e fai clic su Fine.
Puoi vedere nella finestra di progettazione che è stata creata una vista e puoi usarla nel programma come un'entità.
In Esplora soluzioni è possibile vedere che anche la classe MyView viene generata dal database.
Facciamo un esempio in cui tutti i dati vengono recuperati dalla visualizzazione. Di seguito è riportato il codice:
class Program {
static void Main(string[] args) {
using (var db = new UniContextEntities()) {
var query = from b in db.MyViews
orderby b.FirstMidName select b;
Console.WriteLine("All student in the database:");
foreach (var item in query) {
Console.WriteLine(item.FirstMidName + " " + item.LastName);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
Quando il codice sopra viene eseguito, riceverai il seguente output:
All student in the database:
Ali Khan
Arturo finand
Bill Gates
Carson Alexander
Gytis Barzdukas
Laura Norman
Meredith Alonso
Nino Olivetto
Peggy Justice
Yan Li
Press any key to exit...
Ti consigliamo di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
Un indice è una struttura di dati su disco basata su tabelle e viste. Gli indici rendono il recupero dei dati più veloce ed efficiente, nella maggior parte dei casi. Tuttavia, il sovraccarico di una tabella o una vista con gli indici potrebbe influire in modo spiacevole sulle prestazioni di altre operazioni come inserimenti o aggiornamenti.
L'indicizzazione è la nuova funzionalità in entity framework in cui è possibile migliorare le prestazioni dell'applicazione Code First riducendo il tempo necessario per eseguire query sui dati dal database.
Puoi aggiungere indici al tuo database usando il Index attributo e sovrascrivere il valore predefinito Unique e Clustered impostazioni per ottenere l'indice più adatto al tuo scenario.
Diamo un'occhiata al codice seguente in cui l'attributo Index viene aggiunto nella classe Course per CourseID.
public partial class Course {
public Course() {
this.Enrollments = new HashSet<Enrollment>();
}
[Index]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public byte[] VersionNo { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
La chiave creata sopra è non univoca, non in cluster. Sono disponibili sovraccarichi per sovrascrivere questi valori predefiniti:
Per rendere un indice un indice cluster, è necessario specificare IsClustered = true
Allo stesso modo, puoi anche rendere un indice un indice univoco specificando IsUnique = true
Diamo un'occhiata al seguente codice C # in cui un indice è clusterizzato e univoco.
public partial class Course {
public Course() {
this.Enrollments = new HashSet<Enrollment>();
}
[Index(IsClustered = true, IsUnique = true)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public byte[] VersionNo { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
L'attributo Index può essere utilizzato per creare un indice univoco nel database. Tuttavia, questo non significa che EF sarà in grado di ragionare sull'unicità della colonna quando si tratta di relazioni, ecc. Questa caratteristica è generalmente indicata come supporto per "vincoli unici".
Entity Framework consente di utilizzare stored procedure in Entity Data Model anziché o in combinazione con la generazione automatica dei comandi.
È possibile utilizzare le stored procedure per eseguire la logica predefinita sulle tabelle del database e molte organizzazioni dispongono di criteri che richiedono l'utilizzo di queste stored procedure.
Può anche specificare che EF deve utilizzare le stored procedure per inserire, aggiornare o eliminare entità.
Sebbene i comandi creati dinamicamente siano sicuri, efficienti e generalmente validi o migliori di quelli che potresti scrivere tu stesso, ci sono molti casi in cui esistono già procedure memorizzate e le pratiche della tua azienda possono limitare l'uso diretto delle tabelle.
In alternativa, potresti semplicemente voler avere un controllo esplicito su ciò che viene eseguito nell'archivio e preferire creare stored procedure.
L'esempio seguente crea un nuovo progetto da File → Nuovo → Progetto.
Step 1 - Seleziona l'applicazione console dal riquadro centrale e inserisci StoredProceduresDemo nel campo del nome.
Step 2 - In Esplora server fare clic con il pulsante destro del mouse sul database.
Step 3 - Seleziona Nuova query e inserisci il seguente codice nell'editor T-SQL per aggiungere una nuova tabella nel tuo database.
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id =
OBJECT_ID(N'[dbo].[StudentGrade]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[StudentGrade](
[EnrollmentID] [int] IDENTITY(1,1) NOT NULL,
[CourseID] [int] NOT NULL,
[StudentID] [int] NOT NULL,
[Grade] [decimal](3, 2) NULL,
CONSTRAINT [PK_StudentGrade] PRIMARY KEY CLUSTERED (
[EnrollmentID] ASC
)
WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO
Step 4 - Fai clic con il pulsante destro del mouse sull'editor e seleziona Esegui.
Step 5- Fare clic con il pulsante destro del mouse sul database e fare clic su Aggiorna. Vedrai la tabella appena aggiunta nel tuo database.
Step 6 - In Esplora server, fai nuovamente clic con il pulsante destro del mouse sul database.
Step 7 - Seleziona Nuova query e inserisci il seguente codice nell'editor T-SQL per aggiungere una procedura memorizzata nel tuo database, che restituirà i voti dello studente.
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id =
OBJECT_ID(N'[dbo].[GetStudentGrades]') AND type in (N'P', N'PC'))
BEGIN
EXEC dbo.sp_executesql @statement = N'
CREATE PROCEDURE [dbo].[GetStudentGrades]
@StudentID int
AS
SELECT EnrollmentID, Grade, CourseID, StudentID FROM dbo.StudentGrade
WHERE StudentID = @StudentID
'
END
GO
Step 8 - Fai clic con il pulsante destro del mouse sull'editor e seleziona Esegui.
Step 9- Fare clic con il pulsante destro del mouse sul database e fare clic su Aggiorna. Vedrai che una stored procedure viene creata nel tuo database.
Step 10 - Fare clic con il pulsante destro del mouse sul nome del progetto in Esplora soluzioni e selezionare Aggiungi → Nuovo elemento.
Step 11 - Quindi selezionare ADO.NET Entity Data Model nel riquadro Modelli.
Step 12 - Immettere SPModel come nome, quindi fare clic su Aggiungi.
Step 13 - Nella finestra di dialogo Scegli contenuto modello, seleziona Progettazione EF dal database, quindi fai clic su Avanti.
Step 14 - Seleziona il tuo database e fai clic su Avanti.
Step 15 - Nella finestra di dialogo Scegli gli oggetti del database fare clic su tabelle, viste.
Step 16 - Selezionare la funzione GetStudentGradesForCourse situata nel nodo Stored procedure e funzioni e fare clic su Fine.
Step 17 - Seleziona Visualizza → Altre finestre → Browser Entity Data Model e fai clic con il pulsante destro del mouse su GetStudentGrades in Importazioni di funzioni e seleziona Modifica.
Produrrà la seguente finestra di dialogo.
Step 18 - Fare clic sul pulsante di opzione Entità e selezionare StudentGrade dalla casella combinata come tipo di ritorno di questa procedura memorizzata e fare clic su OK.
Diamo un'occhiata al seguente codice C # in cui verranno recuperati tutti i voti passando l'ID studente come parametro nella stored procedure GetStudentGrades.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
int studentID = 22;
var studentGrades = context.GetStudentGrades(studentID);
foreach (var student in studentGrades) {
Console.WriteLine("Course ID: {0}, Title: {1}, Grade: {2} ",
student.CourseID, student.Course.Title, student.Grade);
}
Console.ReadKey();
}
}
}
Quando il codice precedente viene compilato ed eseguito, riceverai il seguente output:
Course ID: 4022, Title: Microeconomics, Grade: 3.00
Course ID: 4041, Title: Macroeconomics, Grade: 3.50
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
In questo capitolo, vediamo come apportare modifiche alle entità che non vengono tracciate da un contesto. Le entità che non vengono tracciate da un contesto sono note come entità "disconnesse".
Per la maggior parte delle applicazioni a livello singolo, in cui l'interfaccia utente e i livelli di accesso al database vengono eseguiti nello stesso processo dell'applicazione, probabilmente eseguirai solo operazioni su entità che vengono tracciate da un contesto.
Le operazioni su entità disconnesse sono molto più comuni nelle applicazioni a più livelli.
Le applicazioni a più livelli implicano il recupero di alcuni dati su un server e la restituzione, tramite la rete, a una macchina client.
L'applicazione client quindi manipola questi dati prima di restituirli al server per essere mantenuti.
Di seguito sono riportati i due passaggi che devono essere eseguiti con il grafico di entità disconnessa o anche con una singola entità disconnessa.
Collega le entità con la nuova istanza di contesto e rendi consapevole il contesto di queste entità.
Impostare manualmente gli EntityStates appropriati per queste entità.
Diamo un'occhiata al codice seguente in cui l'entità Student viene aggiunta con due entità Enrollment.
class Program {
static void Main(string[] args) {
var student = new Student {
ID = 1001,
FirstMidName = "Wasim",
LastName = "Akram",
EnrollmentDate = DateTime.Parse("2015-10-10"),
Enrollments = new List<Enrollment> {
new Enrollment{EnrollmentID = 2001,CourseID = 4022, StudentID = 1001 },
new Enrollment{EnrollmentID = 2002,CourseID = 4025, StudentID = 1001 },
}
};
using (var context = new UniContextEntities()) {
context.Students.Add(student);
Console.WriteLine("New Student ({0} {1}): {2}",
student.FirstMidName, student.LastName, context.Entry(student).State);
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0} State: {1}",
enrollment.EnrollmentID, context.Entry(enrollment).State);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
Il codice costruisce una nuova istanza di Student, che fa riferimento anche a due nuove istanze di Enrollment nella relativa proprietà Enrollments.
Quindi il nuovo Studente viene aggiunto a un contesto utilizzando il metodo Aggiungi.
Dopo aver aggiunto lo Student, il codice usa il metodo DbContext.Entry per ottenere l'accesso alle informazioni di rilevamento delle modifiche che Entity Framework ha sul nuovo Student.
Da queste informazioni sul rilevamento delle modifiche, la proprietà State viene utilizzata per scrivere lo stato corrente dell'entità.
Questo processo viene quindi ripetuto per ciascuna delle iscrizioni appena create a cui fa riferimento il nuovo Studente. Se esegui l'applicazione, riceverai il seguente output:
New Student (Wasim Akram): Added
Enrollment ID: 2001 State: Added
Enrollment ID: 2002 State: Added
Press any key to exit...
Mentre DbSet.Add viene usato per indicare a Entity Framework nuove entità, DbSet.Attach viene usato per indicare a Entity Framework le entità esistenti. Il metodo Allega contrassegnerà un'entità nello stato Non modificato.
Diamo un'occhiata al codice C # seguente in cui un'entità disconnessa è collegata a DbContext.
class Program {
static void Main(string[] args) {
var student = new Student {
ID = 1001,
FirstMidName = "Wasim",
LastName = "Akram",
EnrollmentDate = DateTime.Parse("2015-10-10"),
Enrollments = new List<Enrollment> {
new Enrollment { EnrollmentID = 2001, CourseID = 4022, StudentID = 1001 },
new Enrollment { EnrollmentID = 2002, CourseID = 4025, StudentID = 1001 },
}
};
using (var context = new UniContextEntities()) {
context.Students.Attach(student);
Console.WriteLine("New Student ({0} {1}): {2}",
student.FirstMidName, student.LastName, context.Entry(student).State);
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0} State: {1}", enrollment.EnrollmentID,
context.Entry(enrollment).State);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
Quando il codice sopra viene eseguito con il metodo Attach (), riceverai il seguente output.
New Student (Wasim Akram): Unchanged
Enrollment ID: 2001 State: Unchanged
Enrollment ID: 2002 State: Unchanged
Press any key to exit...
In questo capitolo, impareremo come mappare funzioni con valori di tabella (TVF) usando Entity Framework Designer e come chiamare un TVF da una query LINQ.
I TVF sono attualmente supportati solo nel flusso di lavoro Database First.
È stato introdotto per la prima volta in Entity Framework versione 5.
Per utilizzare i TVF è necessario targetizzare .NET Framework 4.5 o versioni successive.
È molto simile alle stored procedure ma con una differenza fondamentale, ovvero il risultato di un TVF è componibile. Ciò significa che i risultati di un TVF possono essere utilizzati in una query LINQ mentre i risultati di una stored procedure non possono.
Diamo un'occhiata al seguente esempio di creazione di un nuovo progetto da File → Nuovo → Progetto.
Step 1 - Seleziona l'applicazione console dal riquadro centrale e inserisci TableValuedFunctionDemo nel campo del nome.
Step 2 - In Esplora server fare clic con il pulsante destro del mouse sul database.
Step 3 - Seleziona Nuova query e inserisci il seguente codice nell'editor T-SQL per aggiungere una nuova tabella nel tuo database.
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id =
OBJECT_ID(N'[dbo].[StudentGrade]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[StudentGrade](
[EnrollmentID] [int] IDENTITY(1,1) NOT NULL,
[CourseID] [int] NOT NULL,
[StudentID] [int] NOT NULL,
[Grade] [decimal](3, 2) NULL,
CONSTRAINT [PK_StudentGrade] PRIMARY KEY CLUSTERED ([EnrollmentID] ASC)
WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
END
GO
Step 4 - Fai clic con il pulsante destro del mouse sull'editor e seleziona Esegui.
Step 5- Fare clic con il pulsante destro del mouse sul database e fare clic su Aggiorna. Vedrai la tabella appena aggiunta nel tuo database.
Step 6- Ora crea una funzione che restituirà i voti degli studenti per il corso. Immettere il codice seguente nell'editor T-SQL.
CREATE FUNCTION [dbo].[GetStudentGradesForCourse]
(@CourseID INT)
RETURNS TABLE
RETURN
SELECT [EnrollmentID],
[CourseID],
[StudentID],
[Grade]
FROM [dbo].[StudentGrade]
WHERE CourseID = @CourseID
Step 7 - Fai clic con il pulsante destro del mouse sull'editor e seleziona Esegui.
Ora puoi vedere che la funzione è stata creata.
Step 8 - Fare clic con il pulsante destro del mouse sul nome del progetto in Esplora soluzioni e selezionare Aggiungi → Nuovo elemento.
Step 9 - Quindi selezionare ADO.NET Entity Data Model nel riquadro Modelli.
Step 10 - Immettere TVFModel come nome, quindi fare clic su Aggiungi.
Step 11 - Nella finestra di dialogo Scegli contenuto modello, seleziona Progettazione EF dal database, quindi fai clic su Avanti.
Step 12 - Seleziona il tuo database e fai clic su Avanti.
Step 13 - Nella finestra di dialogo Scegli gli oggetti del database selezionare tabelle, viste.
Step 14 - Selezionare la funzione GetStudentGradesForCourse situata nel nodo Stored procedure e funzioni e fare clic su Fine.
Step 15 - Selezionare Visualizza → Altre finestre → Browser modello dati entità e fare clic con il pulsante destro del mouse su GetStudentGradesForCourse in Importazioni di funzioni e selezionare Modifica.
Vedrai la seguente finestra di dialogo.
Step 16 - Fare clic sul pulsante di opzione Entità e selezionare Iscrizione dalla casella combinata come tipo di ritorno di questa funzione e fare clic su OK.
Diamo un'occhiata al seguente codice C # in cui verranno recuperati tutti i voti degli studenti iscritti al corso ID = 4022 nel database.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
var CourseID = 4022;
// Return all the best students in the Microeconomics class.
var students = context.GetStudentGradesForCourse(CourseID);
foreach (var result in students) {
Console.WriteLine("Student ID: {0}, Grade: {1}",
result.StudentID, result.Grade);
}
Console.ReadKey();
}
}
}
Quando il codice precedente viene compilato ed eseguito, riceverai il seguente output:
Student ID: 1, Grade: 2
Student ID: 4, Grade: 4
Student ID: 9, Grade: 3.5
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
In Entity Framework puoi eseguire query con le tue classi di entità usando LINQ. È inoltre possibile eseguire query utilizzando SQL raw direttamente sul database utilizzando DbCOntext. Le tecniche possono essere applicate allo stesso modo ai modelli creati con Code First ed EF Designer.
Query SQL su entità esistente
Il metodo SqlQuery su DbSet consente di scrivere una query SQL non elaborata che restituirà istanze di entità. Gli oggetti restituiti verranno tracciati dal contesto proprio come lo sarebbero se fossero restituiti da una query LINQ. Ad esempio:
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
var students = context.Students.SqlQuery("SELECT * FROM dbo.Student").ToList();
foreach (var student in students) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}, \tEnrollment Date {2} ",
student.ID, name, student.EnrollmentDate.ToString());
}
Console.ReadKey();
}
}
}
Il codice sopra recupererà tutti gli studenti dal database.
Query SQL per tipi non entità
Una query SQL che restituisce istanze di qualsiasi tipo, inclusi i tipi primitivi, può essere creata utilizzando il metodo SqlQuery sulla classe Database. Ad esempio:
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
var studentNames = context.Database.SqlQuery
<string>("SELECT FirstMidName FROM dbo.Student").ToList();
foreach (var student in studentNames) {
Console.WriteLine("Name: {0}", student);
}
Console.ReadKey();
}
}
}
Comandi SQL al database
Il metodo ExecuteSqlCommnad viene utilizzato per inviare comandi non di query al database, come il comando Inserisci, Aggiorna o Elimina. Diamo un'occhiata al seguente codice in cui il nome dello studente viene aggiornato come ID = 1
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
//Update command
int noOfRowUpdated = context.Database.ExecuteSqlCommand("Update
student set FirstMidName = 'Ali' where ID = 1");
context.SaveChanges();
var student = context.Students.SqlQuery("SELECT * FROM
dbo.Student where ID = 1").Single();
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}, \tEnrollment Date {2} ",
student.ID, name, student.EnrollmentDate.ToString());
Console.ReadKey();
}
}
}
Il codice precedente recupererà il nome di tutti gli studenti dal database.
In Entity Framework, questa funzionalità consentirà di definire una proprietà su una classe di dominio che è un tipo enum e mapparla a una colonna di database di un tipo intero. Entity Framework convertirà quindi il valore del database in e dall'enumerazione pertinente durante la query e il salvataggio dei dati.
I tipi enumerati offrono tutti i tipi di vantaggi quando si lavora con proprietà che hanno un numero fisso di risposte.
La sicurezza e l'affidabilità di un'applicazione aumentano entrambe quando si utilizzano le enumerazioni.
L'enumerazione rende molto più difficile per l'utente commettere errori e problemi come gli attacchi di iniezione sono inesistenti.
In Entity Framework, un'enumerazione può avere i seguenti tipi sottostanti:
- Byte
- Int16
- Int32
- Int64
- SByte
Il tipo sottostante predefinito degli elementi di enumerazione è int.
Per impostazione predefinita, il primo enumeratore ha il valore 0 e il valore di ogni enumeratore successivo viene aumentato di 1.
Diamo un'occhiata al seguente esempio in cui creeremo un'entità in designer e poi aggiungeremo alcune proprietà.
Step 1 - Crea nuovo progetto da File → Nuovo → opzione di menu Progetto.
Step 2 - Nel riquadro di sinistra, seleziona l'applicazione Console.
Step 3 - Immettere EFEnumDemo come nome del progetto e fare clic su OK.
Step 4 - Fare clic con il pulsante destro del mouse sul nome del progetto in Esplora soluzioni e selezionare l'opzione di menu Aggiungi → Nuovo elemento.
Step 5 - Seleziona ADO.NET Entity Data Model nel riquadro Modelli.
Step 6 - Immettere EFEnumModel.edmx per il nome del file, quindi fare clic su Aggiungi.
Step 7 - Nella pagina Creazione guidata modello di dati entità, selezionare Modello di progettazione EF vuoto.
Step 8 - Fare clic su Fine
Step 9 - Quindi fare clic con il tasto destro sulla finestra di progettazione e selezionare Aggiungi → Entità.
La finestra di dialogo Nuova entità viene visualizzata come mostrato nell'immagine seguente.
Step 10 - Immettere Department come nome entità e DeptID come nome proprietà, lasciare il tipo di proprietà su Int32 e fare clic su OK.
Step 11 - Fare clic con il pulsante destro del mouse sull'entità e selezionare Aggiungi nuovo → Proprietà scalare.
Step 12 - Rinomina la nuova proprietà in DeptName.
Step 13 - Modificare il tipo della nuova proprietà in Int32 (per impostazione predefinita, la nuova proprietà è di tipo String).
Step 14 - Per modificare il tipo, aprire la finestra Proprietà e modificare la proprietà Type in Int32.
Step 15 - In Entity Framework Designer, fai clic con il pulsante destro del mouse sulla proprietà Name, seleziona Converti in enumerazione.
Step 16 - Nella finestra di dialogo Aggiungi tipo di enumerazione, immettere DepartmentNames per Enum Type Name, modificare il tipo sottostante in Int32, quindi aggiungere i seguenti membri al tipo: Physics, Chemistry, Computer ed Economics.
Step 17 - Fare clic su Ok.
Se passi alla finestra Browser modello, vedrai che il tipo è stato aggiunto anche al nodo Tipi enumerazione.
Generiamo il database dal modello seguendo tutti i passaggi menzionati nel capitolo sull'approccio Model First.
Step 1 - Fare clic con il pulsante destro del mouse sulla superficie Entity Designer e selezionare Genera database da modello.
Viene visualizzata la finestra di dialogo Scegli la connessione dati della Creazione guidata database.
Step 2 - Fare clic sul pulsante Nuova connessione.
Step 3 - Immettere il nome del server e EnumDemo per il database e fare clic su OK.
Step 4 - Verrà visualizzata una finestra di dialogo che chiede se si desidera creare un nuovo database, fare clic su Sì.
Step 5- Fare clic su Avanti e la Creazione guidata database genererà il linguaggio di definizione dei dati (DDL) per la creazione di un database. Ora fai clic su Fine.
Step 6 - Fare clic con il pulsante destro del mouse su T-SQL Editor e selezionare Esegui.
Step 7 - Per visualizzare lo schema generato, fare clic con il pulsante destro del mouse sul nome del database in Esplora oggetti di SQL Server e selezionare Aggiorna.
Vedrai la tabella Dipartimenti nel database.
Diamo un'occhiata al seguente esempio in cui vengono aggiunti e salvati alcuni nuovi oggetti Department al contesto. E poi recupera il reparto Computer.
class Program {
static void Main(string[] args) {
using (var context = new EFEnumModelContainer()) {
context.Departments.Add(new Department { DeptName = DepartmentNames.Physics});
context.Departments.Add(new Department { DeptName = DepartmentNames.Computer});
context.Departments.Add(new Department { DeptName = DepartmentNames.Chemistry});
context.Departments.Add(new Department { DeptName = DepartmentNames.Economics});
context.SaveChanges();
var department = (
from d in context.Departments
where d.DeptName == DepartmentNames.Computer
select d
).FirstOrDefault();
Console.WriteLine(
"Department ID: {0}, Department Name: {1}",
department.DeptID, department.DeptName
);
Console.ReadKey();
}
}
}
Quando il codice sopra viene eseguito, riceverai il seguente output:
Department ID: 2, Department Name: Computer
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
Asynchronous programmingimplica l'esecuzione di operazioni in background in modo che il thread principale possa continuare le proprie operazioni. In questo modo il thread principale può mantenere reattiva l'interfaccia utente mentre il thread in background elabora l'attività in corso.
Entity Framework 6.0 supporta operazioni asincrone per l'esecuzione di query e il salvataggio dei dati.
Le operazioni asincrone possono aiutare la tua applicazione nei seguenti modi:
- Rendi la tua applicazione più reattiva alle interazioni degli utenti
- Migliora le prestazioni complessive della tua applicazione
È possibile eseguire operazioni asincrone in vari modi. Ma le parole chiave asincrone / attesa sono state introdotte in .NET Framework 4.5, il che semplifica il tuo lavoro.
L'unica cosa che devi seguire è il modello async / await come illustrato dal frammento di codice seguente.
Diamo un'occhiata al seguente esempio (senza usare async / await) in cui il metodo DatabaseOperations salva un nuovo studente nel database e poi recupera tutti gli studenti dal database e alla fine viene stampato un messaggio aggiuntivo sulla console.
class Program {
static void Main(string[] args) {
Console.WriteLine("Database Operations Started");
DatabaseOperations();
Console.WriteLine();
Console.WriteLine("Database Operations Completed");
Console.WriteLine();
Console.WriteLine("Entity Framework Tutorials");
Console.ReadKey();
}
public static void DatabaseOperations() {
using (var context = new UniContextEntities()) {
// Create a new student and save it
context.Students.Add(new Student {
FirstMidName = "Akram",
LastName = "Khan",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())});
Console.WriteLine("Calling SaveChanges.");
context.SaveChanges();
Console.WriteLine("SaveChanges completed.");
// Query for all Students ordered by first name
var students = (from s in context.Students
orderby s.FirstMidName select s).ToList();
// Write all students out to Console
Console.WriteLine();
Console.WriteLine("All Student:");
foreach (var student in students) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine(" " + name);
}
}
}
}
Quando il codice sopra viene eseguito, riceverai il seguente output:
Calling SaveChanges.
SaveChanges completed.
All Student:
Akram Khan
Ali Khan
Ali Alexander
Arturo Anand
Bill Gates
Gytis Barzdukas
Laura Nornan
Meredith fllonso
Nino Olioetto
Peggy Justice
Yan Li
Entity Framework Tutorials
Usiamo le nuove parole chiave async e await e apportiamo le seguenti modifiche a Program.cs
Aggiungi lo spazio dei nomi System.Data.Entity che fornirà metodi di estensione asincroni EF.
Aggiungi lo spazio dei nomi System.Threading.Tasks che ci consentirà di utilizzare il tipo di attività.
Aggiornare DatabaseOperations da contrassegnare come async e restituisci un file Task.
Chiama la versione Async di SaveChanges e attendi il suo completamento.
Chiama la versione asincrona di ToList e attendi il risultato.
class Program {
static void Main(string[] args) {
var task = DatabaseOperations();
Console.WriteLine();
Console.WriteLine("Entity Framework Tutorials");
task.Wait();
Console.ReadKey();
}
public static async Task DatabaseOperations() {
using (var context = new UniContextEntities()) {
// Create a new blog and save it
context.Students.Add(new Student {
FirstMidName = "Salman",
LastName = "Khan",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())});
Console.WriteLine("Calling SaveChanges.");
await context.SaveChangesAsync();
Console.WriteLine("SaveChanges completed.");
// Query for all Students ordered by first name
var students = await (from s in context.Students
orderby s.FirstMidName select s).ToListAsync();
// Write all students out to Console
Console.WriteLine();
Console.WriteLine("All Student:");
foreach (var student in students) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine(" " + name);
}
}
}
}
All'esecuzione, produrrà il seguente output.
Calling SaveChanges.
Entity Framework Tutorials
SaveChanges completed.
All Student:
Akram Khan
Ali Khan
Ali Alexander
Arturo Anand
Bill Gates
Gytis Barzdukas
Laura Nornan
Meredith fllonso
Nino Olioetto
Peggy Justice
Salman Khan
Yan Li
Ora che il codice è asincrono, puoi osservare un diverso flusso di esecuzione del tuo programma.
SaveChanges inizia a inviare il nuovo Student al database, quindi il metodo DatabaseOperations restituisce (anche se non ha terminato l'esecuzione) e il flusso del programma nel metodo Main continua.
Il messaggio viene quindi scritto nella console.
Il thread gestito viene bloccato nella chiamata Wait fino al completamento dell'operazione del database. Una volta completato, verrà eseguito il resto delle nostre operazioni di database.
SaveChanges viene completato.
Recupera tutto lo studente dal database e viene scritto nella Console.
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
Entity Framework ora ti consente di trarre vantaggio da Entity Framework senza costringere ogni parte della tua applicazione a essere a conoscenza di Entity Framework, separando le entità dall'infrastruttura. È possibile creare classi che possono concentrarsi sulle proprie regole di business indipendentemente dal modo in cui vengono mantenute (dove vengono archiviati i dati e come i dati passano avanti e indietro tra gli oggetti).
Creazione di entità ignoranti persistenti
Il paragrafo precedente ha descritto un metodo che non ha una conoscenza approfondita della fonte dei dati che consuma. Ciò evidenzia l'essenza dell'ignoranza della persistenza, ovvero quando le tue classi e molti dei nostri livelli applicativi attorno a loro non si preoccupano di come vengono archiviati i dati.
Nella versione .NET 3.5 di Entity Framework, se si desiderava utilizzare classi preesistenti, era necessario modificarle costringendole a derivare da EntityObject.
In .NET 4 questo non è più necessario. Non è necessario modificare le entità per consentire loro di partecipare alle operazioni di Entity Framework.
Questo ci consente di creare applicazioni che abbracciano l'accoppiamento libero e la separazione delle preoccupazioni.
Con questi modelli di codifica, le tue classi si occupano solo dei propri lavori e, molti livelli della tua applicazione, inclusa l'interfaccia utente, non hanno dipendenze dalla logica esterna, come le API di Entity Framework, ma quelle API esterne sono in grado di interagire con il nostro entità.
Esistono 2 modi (connesso e disconnesso) quando si mantiene un'entità con Entity Framework. Entrambi i modi hanno la loro importanza. Nel caso di uno scenario connesso le modifiche vengono tracciate dal contesto, ma nel caso di uno scenario disconnesso è necessario informare il contesto sullo stato dell'entità.
Scenari connessi
Lo scenario connesso è quando un'entità viene recuperata dal database e modificata nello stesso contesto. Per uno scenario connesso, supponiamo di avere un servizio Windows e di eseguire alcune operazioni aziendali con quell'entità, quindi apriremo il contesto, eseguiremo il ciclo di tutte le entità, eseguiremo le nostre operazioni aziendali e quindi salveremo le modifiche con lo stesso contesto aperto all'inizio.
Diamo un'occhiata al seguente esempio in cui gli studenti vengono recuperati dal database e aggiornano il nome degli studenti, quindi salvano le modifiche nel database.
class Program {
static void Main(string[] args) {
using (var context = new MyContext()) {
var studentList = context.Students.ToList();
foreach (var stdnt in studentList) {
stdnt.FirstMidName = "Edited " + stdnt.FirstMidName;
}
context.SaveChanges();
//// Display all Students from the database
var students = (from s in context.Students
orderby s.FirstMidName select s).ToList<Student>();
Console.WriteLine("Retrieve all Students from the database:");
foreach (var stdnt in students) {
string name = stdnt.FirstMidName + " " + stdnt.LastName;
Console.WriteLine("ID: {0}, Name: {1}", stdnt.ID, name);
}
Console.ReadKey();
}
}
}
Quando il codice sopra è stato compilato ed eseguito, riceverai il seguente output e vedrai che la parola modificata è allegata prima del nome come mostrato nell'output seguente.
Retrieve all Students from the database:
ID: 1, Name: Edited Edited Alain Bomer
ID: 2, Name: Edited Edited Mark Upston
Scenari disconnessi
Lo scenario disconnesso è quando un'entità viene recuperata dal database e modificata in un contesto diverso. Supponiamo di voler visualizzare alcuni dati in un livello di presentazione e di utilizzare un'applicazione a più livelli, quindi sarebbe meglio aprire il contesto, recuperare i dati e infine chiudere il contesto. Poiché qui abbiamo recuperato i dati e chiuso il contesto, le entità che abbiamo recuperato non vengono più tracciate e questo è lo scenario disconnesso.
Diamo un'occhiata al codice seguente in cui la nuova entità Student disconnessa viene aggiunta a un contesto usando il metodo Add.
class Program {
static void Main(string[] args) {
var student = new Student {
ID = 1001,
FirstMidName = "Wasim",
LastName = "Akram",
EnrollmentDate = DateTime.Parse( DateTime.Today.ToString())
};
using (var context = new MyContext()) {
context.Students.Add(student);
context.SaveChanges();
//// Display all Students from the database
var students = (from s in context.Students
orderby s.FirstMidName select s).ToList<Student>();
Console.WriteLine("Retrieve all Students from the database:");
foreach (var stdnt in students) {
string name = stdnt.FirstMidName + " " + stdnt.LastName;
Console.WriteLine("ID: {0}, Name: {1}", stdnt.ID, name);
}
Console.ReadKey();
}
}
}
Quando il codice precedente viene compilato ed eseguito, riceverai il seguente output.
Retrieve all Students from the database:
ID: 1, Name: Edited Edited Edited Alain Bomer
ID: 2, Name: Edited Edited Edited Mark Upston
ID: 3, Name: Wasim Akram
LINQ to Entities
Uno dei concetti più importanti per comprendere LINQ to Entities è che si tratta di un linguaggio dichiarativo. L'obiettivo è definire quali informazioni sono necessarie, piuttosto che su come ottenerle.
Significa che puoi dedicare più tempo a lavorare con i dati e meno tempo a cercare di capire il codice sottostante richiesto per eseguire attività come l'accesso al database.
È importante capire che i linguaggi dichiarativi in realtà non rimuovono alcun controllo dallo sviluppatore, ma aiutano lo sviluppatore a focalizzare l'attenzione su ciò che è importante.
Parole chiave essenziali per LINQ to Entities
È importante conoscere le parole chiave di base utilizzate per creare una query LINQ. Ci sono solo poche parole chiave da ricordare, ma puoi combinarle in vari modi per ottenere risultati specifici. Il seguente elenco contiene queste parole chiave di base e fornisce una semplice descrizione di ciascuna di esse.
Sr. No. | Parola chiave e descrizione |
---|---|
1 | Ascending Specifica che un'operazione di ordinamento viene eseguita dall'elemento minimo (o più basso) di un intervallo all'elemento più alto di un intervallo. Questa è normalmente l'impostazione predefinita. Ad esempio, quando si esegue un ordinamento alfabetico, l'ordinamento sarà compreso nell'intervallo dalla A alla Z. |
2 | By Specifica il campo o l'espressione utilizzata per implementare un raggruppamento. Il campo o l'espressione definisce una chiave utilizzata per eseguire l'attività di raggruppamento. |
3 | Descending Specifica che un'operazione di ordinamento viene eseguita dall'elemento più grande (o più alto) di un intervallo all'elemento più basso di un intervallo. Ad esempio, quando si esegue un ordinamento alfabetico, l'ordinamento sarà compreso tra Z e A. |
4 | Equals Utilizzato tra le clausole sinistra e destra di un'istruzione join per unire l'origine dati contestuale primaria all'origine dati contestuale secondaria. Il campo o l'espressione a sinistra della parola chiave equals specifica l'origine dati primaria, mentre il campo o l'espressione a destra della parola chiave equals specifica l'origine dati secondaria. |
5 | From Specifica l'origine dati utilizzata per ottenere le informazioni richieste e definisce una variabile di intervallo. Questa variabile ha lo stesso scopo di una variabile utilizzata per l'iterazione in un ciclo. |
6 | Group Organizza l'output in gruppi utilizzando il valore chiave specificato. Utilizza più clausole di gruppo per creare più livelli di organizzazione dell'output. L'ordine delle clausole di gruppo determina la profondità alla quale un particolare valore di chiave appare nell'ordine di raggruppamento. Combina questa parola chiave con by per creare un contesto specifico. |
7 | In Utilizzato in molti modi. In questo caso, la parola chiave determina l'origine database contestuale utilizzata per una query. Quando si lavora con un join, la parola chiave in viene utilizzata per ciascuna origine database contestuale utilizzata per il join. |
8 | Into Specifica un identificatore che puoi usare come riferimento per clausole di query LINQ come join, group e select. |
9 | Join Crea una singola origine dati da due origini dati correlate, ad esempio in un'impostazione principale / dettagliata. Un join può specificare un join interno, di gruppo o sinistro-esterno, con il join interno come predefinito. Puoi leggere ulteriori informazioni sui join su msdn.microsoft.com |
10 | Let Definisce una variabile di intervallo che è possibile utilizzare per archiviare i risultati della sottoespressione in un'espressione di query. In genere, la variabile di intervallo viene utilizzata per fornire un output enumerato aggiuntivo o per aumentare l'efficienza di una query (in modo che una particolare attività, come trovare il valore minuscolo di una stringa, non debba essere eseguita più di una volta). |
11 | On Specifica il campo o l'espressione utilizzata per implementare un join. Il campo o l'espressione definisce un elemento comune a entrambe le origini dati contestuali. |
12 | Orderby Crea un ordinamento per la query. È possibile aggiungere la parola chiave crescente o decrescente per controllare l'ordine dell'ordinamento. Utilizza più clausole orderby per creare più livelli di ordinamento. L'ordine delle clausole orderby determina l'ordine in cui vengono gestite le espressioni di ordinamento, quindi l'utilizzo di un ordine diverso produrrà un output diverso. |
13 | Where Definisce cosa LINQ deve recuperare dall'origine dati. Utilizzi una o più espressioni booleane per definire le specifiche di cosa recuperare. Le espressioni booleane vengono separate l'una dall'altra utilizzando && (AND) e || (OR) operatori. |
14 | Select Determina l'output della query LINQ specificando le informazioni da restituire. Questa istruzione definisce il tipo di dati degli elementi restituiti da LINQ durante il processo di iterazione. |
Proiezione
Le query di proiezione migliorano l'efficienza della tua applicazione, recuperando solo campi specifici dal tuo database.
Una volta che hai i dati, potresti voler proiettarli o filtrarli secondo necessità per modellare i dati prima dell'output.
Il compito principale di qualsiasi espressione LINQ to Entities è ottenere i dati e fornirli come output.
La sezione "Sviluppo di query LINQ to Entities" di questo capitolo mostra le tecniche per eseguire questa attività di base.
Diamo un'occhiata al seguente codice in cui verrà recuperato l'elenco degli studenti.
using (var context = new UniContextEntities()) {
var studentList = from s in context.Students select s;
foreach (var student in studentList) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID : {0}, Name: {1}", student.ID, name);
}
}
Oggetto singolo
Per recuperare un singolo oggetto studente è possibile utilizzare i metodi enumerabili First () o FirstOrDefault che restituiscono il primo elemento di una sequenza. La differenza tra First e FirstOrDefault è che First () genererà un'eccezione, se non ci sono dati di risultato per i criteri forniti, mentre FirstOrDefault () restituisce il valore predefinito null, se non ci sono dati di risultato. Nello snippet di codice seguente verrà recuperato il primo studente dell'elenco il cui nome è Ali.
using (var context = new UniContextEntities()) {
var student = (from s in context.Students where s.FirstMidName
== "Ali" select s).FirstOrDefault<Student>();
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID : {0}, Name: {1}", student.ID, name);
}
Puoi anche usare Single () o SingleOrDefault per ottenere un singolo oggetto studente che restituisce un singolo elemento specifico di una sequenza. Nell'esempio seguente, viene recuperato un singolo studente il cui ID è 2.
using (var context = new UniContextEntities()) {
var student = (from s in context.Students where s.ID
== 2 select s).SingleOrDefault<Student>();
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID : {0}, Name: {1}", student.ID, name);
Console.ReadKey();
}
Elenco di oggetti
Se vuoi recuperare l'elenco degli studenti il cui nome è Ali, puoi usare il metodo enumerabile ToList ().
using (var context = new UniContextEntities()) {
var studentList = (from s in context.Students where s.FirstMidName
== "Ali" select s).ToList();
foreach (var student in studentList) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID : {0}, Name: {1}", student.ID, name);
}
Console.ReadKey();
}
Ordine
Per recuperare dati / elenchi in un ordine particolare è possibile utilizzare la parola chiave orderby. Nel codice seguente, l'elenco degli snippet degli studenti verrà recuperato in ordine crescente.
using (var context = new UniContextEntities()) {
var studentList = (from s in context.Students orderby
s.FirstMidName ascending select s).ToList();
foreach (var student in studentList) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID : {0}, Name: {1}", student.ID, name);
}
Console.ReadKey();
}
Query Standard vs Projection Entity Framework
Supponiamo di avere un modello Student che contiene ID, FirstMidName, LastName e EnrollmentDate. Se desideri restituire un elenco di Studenti, una query standard restituirà tutti i campi. Ma se vuoi solo ottenere un elenco di studenti che contengono i campi ID, FirstMidName e LastName. Qui è dove dovresti usare una query di proiezione. Di seguito è riportato il semplice esempio di query di proiezione.
using (var context = new UniContextEntities()) {
var studentList = from s in context.Students
orderby s.FirstMidName ascending
where s.FirstMidName == "Ali"
select new {s.ID, s.FirstMidName, s.LastName};
foreach (var student in studentList) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID : {0}, Name: {1}", student.ID, name);
}
Console.ReadKey();
}
La query di proiezione precedente esclude il campo EnrollmentDate. Questo renderà la tua applicazione molto più veloce.
In Entity Framework 6.0 viene introdotta una nuova funzionalità nota come Logging SQL. Mentre lavora con Entity Framework, invia comandi o una query SQL equivalente al database per eseguire operazioni CRUD (creazione, lettura, aggiornamento ed eliminazione).
Questa funzionalità di Entity Framework consiste nell'acquisire una query SQL equivalente generata internamente da Entity Framework e fornirla come output.
Prima di Entity Framework 6, ogni volta che era necessario tracciare query e comandi del database, lo sviluppatore non aveva altra scelta che utilizzare un'utilità di traccia di terze parti o uno strumento di traccia del database.
In Entity Framework 6, questa nuova funzionalità fornisce un modo semplice registrando tutte le operazioni eseguite da Entity Framework.
Tutte le attività eseguite da Entity Framework vengono registrate utilizzando DbContext.Database.Log.
Diamo un'occhiata al seguente codice in cui un nuovo studente viene aggiunto al database.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
context.Database.Log = Console.Write;
// Create a new student and save it
context.Students.Add(new Student {
FirstMidName = "Salman",
LastName = "Khan",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
context.SaveChanges();
Console.ReadKey();
}
}
}
Quando il codice precedente viene eseguito, riceverai il seguente output, che in realtà è il registro di tutte le attività eseguite da EF nel codice precedente.
Opened connection at 10/28/2015 6:27:35 PM +05:00
Started transaction at 10/28/2015 6:27:35 PM +05:00
INSERT [dbo].[Student]([LastName], [FirstMidName], [EnrollmentDate])
VALUES (@0, @1, @2)
SELECT [ID]
FROM [dbo].[Student]
WHERE @@ROWCOUNT > 0 AND [ID] = scope_identity()
-- @0: 'Khan' (Type = String, Size = -1)
-- @1: 'Salman' (Type = String, Size = -1)
-- @2: '10/28/2015 12:00:00 AM' (Type = DateTime)
-- Executing at 10/28/2015 6:27:35 PM +05:00
-- Completed in 5 ms with result: SqlDataReader
Committed transaction at 10/28/2015 6:27:35 PM +05:00
Closed connection at 10/28/2015 6:27:35 PM +05:00
Quando la proprietà Log è impostata, vengono registrate le seguenti attività:
SQL per tutti i diversi tipi di comandi, ad esempio query, inclusi inserimenti, aggiornamenti ed eliminazioni generati come parte di SaveChanges
Parameters
Indica se il comando viene eseguito in modo asincrono
Un timestamp che indica quando è iniziata l'esecuzione del comando
Il comando è stato completato correttamente o non è riuscito
Qualche indicazione del valore del risultato
Il tempo approssimativo impiegato per eseguire il comando
Accesso in un altro luogo
Se si dispone già di un framework di registrazione e definisce un metodo di registrazione, è possibile anche registrarlo in un altro posto.
Diamo un'occhiata al seguente esempio in cui abbiamo un'altra classe MyLogger.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
context.Database.Log = s ⇒ MyLogger.Log("EFLoggingDemo", s);
// Create a new student and save it
context.Students.Add(new Student {
FirstMidName = "Salman",
LastName = "Khan",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
context.SaveChanges();
Console.ReadKey();
}
}
}
public class MyLogger {
public static void Log(string application, string message) {
Console.WriteLine("Application: {0}, EF Message: {1} ",application, message);
}
}
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
In Entity Framework 6.0 è disponibile un'altra nuova funzionalità nota come Interceptoro intercettazione. Il codice di intercettazione si basa sul concetto diinterception interfaces. Ad esempio, l'interfaccia IDbCommandInterceptor definisce i metodi che vengono chiamati prima che EF esegua una chiamata a ExecuteNonQuery, ExecuteScalar, ExecuteReader e ai metodi correlati.
Entity Framework può davvero brillare usando l'intercettazione. Utilizzando questo approccio è possibile acquisire molte più informazioni in modo transitorio senza dover disfare il codice.
Per implementarlo, è necessario creare il proprio intercettore personalizzato e registrarlo di conseguenza.
Una volta creata una classe che implementa l'interfaccia IDbCommandInterceptor, è possibile registrarla con Entity Framework utilizzando la classe DbInterception.
L'interfaccia IDbCommandInterceptor ha sei metodi ed è necessario implementare tutti questi metodi. Di seguito sono riportate le implementazioni di base di questi metodi.
Diamo un'occhiata al seguente codice in cui è implementata l'interfaccia IDbCommandInterceptor.
public class MyCommandInterceptor : IDbCommandInterceptor {
public static void Log(string comm, string message) {
Console.WriteLine("Intercepted: {0}, Command Text: {1} ", comm, message);
}
public void NonQueryExecuted(DbCommand command,
DbCommandInterceptionContext<int> interceptionContext) {
Log("NonQueryExecuted: ", command.CommandText);
}
public void NonQueryExecuting(DbCommand command,
DbCommandInterceptionContext<int> interceptionContext) {
Log("NonQueryExecuting: ", command.CommandText);
}
public void ReaderExecuted(DbCommand command,
DbCommandInterceptionContext<DbDataReader> interceptionContext) {
Log("ReaderExecuted: ", command.CommandText);
}
public void ReaderExecuting(DbCommand command,
DbCommandInterceptionContext<DbDataReader> interceptionContext) {
Log("ReaderExecuting: ", command.CommandText);
}
public void ScalarExecuted(DbCommand command,
DbCommandInterceptionContext<object> interceptionContext) {
Log("ScalarExecuted: ", command.CommandText);
}
public void ScalarExecuting(DbCommand command,
DbCommandInterceptionContext<object> interceptionContext) {
Log("ScalarExecuting: ", command.CommandText);
}
}
Registrazione degli intercettori
Una volta creata una classe che implementa una o più interfacce di intercettazione, è possibile registrarla con EF utilizzando la classe DbInterception come illustrato nel codice seguente.
DbInterception.Add(new MyCommandInterceptor());
Gli intercettatori possono anche essere registrati a livello di dominio dell'app usando la configurazione basata sul codice DbConfiguration come mostrato nel codice seguente.
public class MyDBConfiguration : DbConfiguration {
public MyDBConfiguration() {
DbInterception.Add(new MyCommandInterceptor());
}
}
Puoi anche configurare il file di configurazione dell'interceptor usando il codice -
<entityFramework>
<interceptors>
<interceptor type = "EFInterceptDemo.MyCommandInterceptor, EFInterceptDemo"/>
</interceptors>
</entityFramework>
Il supporto del tipo spaziale è stato introdotto in Entity Framework 5. È incluso anche un set di operatori per consentire alle query di analizzare i dati spaziali. Ad esempio, una query può filtrare in base alla distanza tra due posizioni geografiche.
Entity Framework consentirà di esporre nuovi tipi di dati spaziali come proprietà nelle classi e di mapparli a colonne spaziali nel database.
Sarai anche in grado di scrivere query LINQ che fanno uso degli operatori spaziali per filtrare, ordinare e raggruppare in base ai calcoli spaziali eseguiti nel database.
Esistono due principali tipi di dati spaziali:
Il tipo di dati geography memorizza i dati ellissoidali, ad esempio, le coordinate di latitudine e longitudine GPS.
Il tipo di dati della geometria rappresenta il sistema di coordinate euclideo (piatto).
Diamo uno sguardo al seguente esempio di campo da cricket.
Step 1 - Crea nuovo progetto da File → Nuovo → opzione di menu Progetto.
Step 2 - Nel riquadro di sinistra, seleziona l'applicazione Console.
Step 3 - Fare clic con il pulsante destro del mouse sul nome del progetto e selezionare Gestisci pacchetti NuGet ...
Step 4 - Installa Entity Framework.
Step 5 - Aggiungere il riferimento all'assembly System.Data.Entity e aggiungere anche l'istruzione using System.Data.Spatial per i tipi di dati spaziali.
Step 6 - Aggiungere la seguente classe nel file Program.cs.
public class CricketGround {
public int ID { get; set; }
public string Name { get; set; }
public DbGeography Location { get; set; }
}
Step 7 - Oltre a definire le entità, è necessario definire una classe che deriva da DbContext ed espone le proprietà DbSet <TEntity>.
In Program.cs aggiungere la definizione del contesto.
public partial class CricketGroundContext : DbContext {
public DbSet<CricketGround> CricketGrounds { get; set; }
}
Step 8 - Aggiungi il seguente codice nella funzione Main, che aggiungerà due nuovi oggetti CricketGround al contesto.
class Program {
static void Main(string[] args) {
using (var context = new CricketGroundContext()) {
context.CricketGrounds.Add(new CricketGround() {
Name = "Shalimar Cricket Ground",
Location = DbGeography.FromText("POINT(-122.336106 47.605049)"),
});
context.CricketGrounds.Add(new CricketGround() {
Name = "Marghazar Stadium", Location = DbGeography
.FromText("POINT(-122.335197 47.646711)"),
});
context.SaveChanges();
var myLocation = DbGeography.FromText("POINT(-122.296623 47.640405)");
var cricketGround = (from cg in context.CricketGrounds
orderby cg.Location.Distance(myLocation) select cg).FirstOrDefault();
Console.WriteLine("The closest Cricket Ground to you is: {0}.", cricketGround.Name);
}
}
}
Le proprietà spaziali vengono inizializzate utilizzando il metodo DbGeography.FromText. Il punto geografico rappresentato come WellKnownText viene passato al metodo e quindi salva i dati. Dopo quell'oggetto CricketGround verrà recuperato dove la sua posizione è più vicina alla posizione specificata.
Quando il codice sopra viene eseguito, riceverai il seguente output:
The closest Cricket Ground to you is: Marghazar Stadium
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
L'ereditarietà consente di creare modelli complessi che riflettono meglio il modo in cui pensano gli sviluppatori e riducono anche il lavoro necessario per interagire con tali modelli. L'ereditarietà usata con le entità ha lo stesso scopo dell'ereditarietà usata con le classi, quindi gli sviluppatori conoscono già le basi di come funziona questa funzionalità.
Diamo uno sguardo al seguente esempio e creando un nuovo progetto di applicazione console.
Step 1 - Aggiungi ADO.NET Entity Data Model facendo clic con il pulsante destro del mouse sul nome del progetto e seleziona Aggiungi → Nuovo elemento ...
Step 2 - Aggiungi un'entità e chiamala Persona seguendo tutti i passaggi menzionati nel capitolo Approccio Model First.
Step 3 - Aggiungi alcune proprietà scalari come mostrato nell'immagine seguente.
Step 4 - Aggiungeremo altre due entità Student e Teacher, che erediterà le proprietà dalla tabella Person.
Step 5 − Now add Student entity and select Person from the Base type combobox as shown in the following image.
Step 6 − Similarly add Teacher entity.
Step 7 − Now add EnrollmentDate scalar property to student entity and HireDate property to Teacher entity.
Step 8 − Let's go ahead and generate the database.
Step 9 − Right click on the design surface and select Generate Database from Model…
Step 10 − To create new Database click on New Connection…The following dialog will open. Click OK.
Step 11 − Click Finish. This will add *.edmx.sql file in the project. You can execute DDL scripts in Visual Studio by opening .sql file. Now right-click and select Execute.
Step 12 − Go to the server explorer you will see that the database is created with three tables which are specified.
Step 13 − You can also see that the following domain classes are also generated automatically.
public partial class Person {
public int ID { get; set; }
public string FirstMidName { get; set; }
public string LastName { get; set; }
}
public partial class Student : Person {
public System.DateTime EnrollmentDate { get; set; }
}
public partial class Teacher : Person {
public System.DateTime HireDate { get; set; }
}
Following is the Context class.
public partial class InheritanceModelContainer : DbContext {
public InheritanceModelContainer() :
base("name = InheritanceModelContainer") {}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Person> People { get; set; }
}
Let’s add some Students and Teachers to the database and then retrieve it from the database.
class Program {
static void Main(string[] args) {
using (var context = new InheritanceModelContainer()) {
var student = new Student {
FirstMidName = "Meredith",
LastName = "Alonso",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
};
context.People.Add(student);
var student1 = new Student {
FirstMidName = "Arturo",
LastName = "Anand",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
};
context.People.Add(student1);
var techaer = new Teacher {
FirstMidName = "Peggy",
LastName = "Justice",
HireDate = DateTime.Parse(DateTime.Today.ToString())
};
context.People.Add(techaer);
var techaer1 = new Teacher {
FirstMidName = "Yan",
LastName = "Li",
HireDate = DateTime.Parse(DateTime.Today.ToString())
};
context.People.Add(techaer1);
context.SaveChanges();
}
}
}
Students and teachers are added in the database. NTo retrieve students and teacher, the OfType method needs to be used, which will return Student and Teacher related to the specified department.
Console.WriteLine("All students in database");
Console.WriteLine("");
foreach (var student in context.People.OfType<Student>()) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}, \tEnrollment Date {2} ",
student.ID, name, student.EnrollmentDate.ToString());
}
Console.WriteLine("");
Console.WriteLine("************************************************************ *****");
Console.WriteLine("");
Console.WriteLine("All teachers in database");
Console.WriteLine("");
foreach (var teacher in context.People.OfType<Teacher>()) {
string name = teacher.FirstMidName + " " + teacher.LastName;
Console.WriteLine("ID: {0}, Name: {1}, \tHireDate {2} ",
teacher.ID, name, teacher.HireDate.ToString());
}
Console.WriteLine("");
Console.WriteLine("************************************************************ *****");
Console.ReadKey();
In the first query, when you use OfType<Student>() then you will not be able to access HireDate because HireDate property is part of Teacher Entity and similarly EnrollmentDate property will not be accessible when you use OfType<Teacher>()
When the above code is executed, you will receive the following output −
All students in database
ID: 1, Name: Meredith Alonso, Enrollment Date 10/30/2015 12:00:00 AM
ID: 2, Name: Arturo Anand, Enrollment Date 10/30/2015 12:00:00 AM
*****************************************************************
All teachers in database
ID: 3, Name: Peggy Justice, HireDate 10/30/2015 12:00:00 AM
ID: 4, Name: Yan Li, HireDate 10/30/2015 12:00:00 AM
*****************************************************************
We recommend that you execute the above example in a step-by-step manner for better understanding.
In Entity Framework 5 and previous versions of Entity Framework, the code was split between core libraries (primarily System.Data.Entity.dll) shipped as part of the .NET Framework, and the additional libraries (primarily EntityFramework.dll) was distributed and shipped using NuGet as shown in the following diagram.
In Entity Framework 6, the core APIs which were previously part of .NET framework are also shipped and distributed as a part of NuGet package.
This was necessary to allow Entity Framework to be made open source. However, as a consequence applications will need to be rebuilt whenever there is a need to migrate or upgrade your application from older versions of Entity Framework to EF 6.
The migration process is straightforward if your application uses DbContext, which was shipped in EF 4.1 and later. But if your application is ObjectContext then little more work is required.
Let’s take a look at the following steps you need to go through to upgrade an existing application to EF6.
Step 1 − The first step is to target .NET Framework 4.5.2 and later right click on your project and select properties.
Step 2 − Right click on your project again and select Manage NuGet Packages...
Step 3 − Under the Online tab select EntityFramework and click Install. Make sure that assembly references to System.Data.Entity.dll are removed.
When you install EF6 NuGet package it should automatically remove any references to System.Data.Entity from your project for you.
Step 4 − If you have any model which is created with the EF Designer, then you will also need to update the code generation templates to generate EF6 compatible code.
Step 5 − In your Solution Explorer under your edmx file, delete existing code-generation templates which typically will be named <edmx_file_name>.tt and <edmx_file_name>.Context.tt.
Step 6 - Apri il tuo modello in EF Designer, fai clic con il pulsante destro del mouse sull'area di progettazione e seleziona Aggiungi elemento di generazione del codice ...
Step 7 - Aggiungere il modello di generazione del codice EF 6.x appropriato.
Inoltre, genererà automaticamente codice compatibile con EF6.
Se le tue applicazioni usano EF 4.1 o versioni successive, non sarà necessario modificare nulla nel codice, perché gli spazi dei nomi per i tipi DbContext e Code First non sono cambiati.
Tuttavia, se l'applicazione utilizza una versione precedente di Entity Framework, i tipi come ObjectContext che erano precedentemente in System.Data.Entity.dll sono stati spostati in nuovi spazi dei nomi.
Step 8 - Sarà necessario aggiornare le direttive using o Import per compilare su EF6.
La regola generale per le modifiche allo spazio dei nomi è che qualsiasi tipo in System.Data. * Viene spostato in System.Data.Entity.Core. *. Di seguito sono riportati alcuni di loro:
- System.Data.EntityException ⇒ System.Data.Entity.Core.EntityException
- System.Data.Objects.ObjectContext ⇒ System.Data.Entity.Core.Objects.ObjectContext;
- System.Data.Objects.DataClasses.RelationshipManager ⇒ System.Data.Entity.Core.Objects.DataClasses.RelationshipManager;
Alcuni tipi si trovano negli spazi dei nomi Core perché non vengono utilizzati direttamente per la maggior parte delle applicazioni basate su DbContext.
- System.Data.EntityState ⇒ System.Data.Entity.EntityState
- System.Data.Objects.DataClasses.EdmFunctionAttribute ⇒ System.Data.Entity.DbFunctionAttribute
Il progetto Entity Framework esistente funzionerà in Entity Framework 6.0 senza modifiche importanti.
Il caricamento desideroso è il processo in base al quale una query per un tipo di entità carica anche le entità correlate come parte della query. Il caricamento desideroso si ottiene utilizzando l'estensioneInclude method.
Significa che la richiesta di dati correlati deve essere restituita insieme ai risultati della query dal database. Esiste una sola connessione stabilita all'origine dati, una maggiore quantità di dati viene restituita nella query iniziale.
Ad esempio, quando si interrogano gli studenti, caricare con entusiasmo le loro iscrizioni. Gli studenti e le loro iscrizioni verranno recuperati in un'unica query.
Diamo uno sguardo al seguente esempio in cui tutti gli studenti con le rispettive iscrizioni vengono recuperati dal database utilizzando il caricamento eager.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
// Load all students and related enrollments
var students = context.Students
.Include(s ⇒ s.Enrollments).ToList();
foreach (var student in students) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}", student.ID, name);
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0}, Course ID: {1}",
enrollment.EnrollmentID, enrollment.CourseID);
}
}
Console.ReadKey();
}
}
}
Quando il codice precedente viene compilato ed eseguito, riceverai il seguente output.
ID: 1, Name: Ali Alexander
Enrollment ID: 1, Course ID: 1050
Enrollment ID: 2, Course ID: 4022
Enrollment ID: 3, Course ID: 4041
ID: 2, Name: Meredith Alonso
Enrollment ID: 4, Course ID: 1045
Enrollment ID: 5, Course ID: 3141
Enrollment ID: 6, Course ID: 2021
ID: 3, Name: Arturo Anand
Enrollment ID: 7, Course ID: 1050
ID: 4, Name: Gytis Barzdukas
Enrollment ID: 8, Course ID: 1050
Enrollment ID: 9, Course ID: 4022
Di seguito sono riportate alcune delle altre forme di query di caricamento desideroso che possono essere utilizzate.
// Load one Student and its related enrollments
var student1 = context.Students
.Where(s ⇒ s.FirstMidName == "Ali")
.Include(s ⇒ s.Enrollments).FirstOrDefault();
// Load all Students and related enrollments
// using a string to specify the relationship
var studentList = context.Students
.Include("Enrollments").ToList();
// Load one Student and its related enrollments
// using a string to specify the relationship
var student = context.Students
.Where(s ⇒ s.FirstMidName == "Salman")
.Include("Enrollments").FirstOrDefault();
Livelli multipli
È anche possibile caricare con impazienza più livelli di entità correlate. Le seguenti query mostrano esempi di Studente, Iscrizioni e Corso.
// Load all Students, all related enrollments, and all related courses
var studentList = context.Students
.Include(s ⇒ s.Enrollments.Select(c ⇒ c.Course)).ToList();
// Load all Students, all related enrollments, and all related courses
// using a string to specify the relationships
var students = context.Students
.Include("Enrollments.Course").ToList();
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
Il caricamento lento è il processo mediante il quale un'entità o una raccolta di entità viene caricata automaticamente dal database la prima volta che si accede a una proprietà che fa riferimento all'entità / entità. Il caricamento lento significa ritardare il caricamento dei dati correlati, fino a quando non lo richiedi specificamente.
Quando si utilizzano i tipi di entità POCO, il caricamento lento si ottiene creando istanze di tipi di proxy derivati e quindi sovrascrivendo le proprietà virtuali per aggiungere l'hook di caricamento.
Il caricamento lento è praticamente l'impostazione predefinita.
Se lasci la configurazione predefinita e non dici esplicitamente a Entity Framework nella tua query che vuoi qualcosa di diverso dal caricamento lento, il caricamento lento è ciò che otterrai.
Ad esempio, quando si utilizza la classe di entità Student, le iscrizioni correlate verranno caricate la prima volta che si accede alla proprietà di navigazione Enrollments.
La proprietà di navigazione deve essere definita come pubblica, virtuale. Il contesto lo faràNOT eseguire il caricamento lento se la proprietà non è definita come virtuale.
Di seguito è riportata una classe Student che contiene la proprietà di navigazione di Enrollments.
public partial class Student {
public Student() {
this.Enrollments = new HashSet<Enrollment>();
}
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public System.DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Diamo un'occhiata a un semplice esempio in cui l'elenco degli studenti viene caricato prima dal database e poi caricherà le iscrizioni di un particolare studente ogni volta che ne avrai bisogno.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
//Loading students only
IList<Student> students = context.Students.ToList<Student>();
foreach (var student in students) {
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}", student.ID, name);
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0}, Course ID: {1}",
enrollment.EnrollmentID, enrollment.CourseID);
}
}
Console.ReadKey();
}
}
}
Quando il codice precedente viene compilato ed eseguito, riceverai il seguente output.
ID: 1, Name: Ali Alexander
Enrollment ID: 1, Course ID: 1050
Enrollment ID: 2, Course ID: 4022
Enrollment ID: 3, Course ID: 4041
ID: 2, Name: Meredith Alonso
Enrollment ID: 4, Course ID: 1045
Enrollment ID: 5, Course ID: 3141
Enrollment ID: 6, Course ID: 2021
ID: 3, Name: Arturo Anand
Enrollment ID: 7, Course ID: 1050
ID: 4, Name: Gytis Barzdukas
Enrollment ID: 8, Course ID: 1050
Enrollment ID: 9, Course ID: 4022
ID: 5, Name: Yan Li
Enrollment ID: 10, Course ID: 4041
ID: 6, Name: Peggy Justice
Enrollment ID: 11, Course ID: 1045
ID: 7, Name: Laura Norman
Enrollment ID: 12, Course ID: 3141
Disattiva il caricamento lento
Il caricamento lento e la serializzazione non si combinano bene e, se non stai attento, puoi finire per interrogare l'intero database solo perché il caricamento lento è abilitato. È buona norma disattivare il caricamento lento prima di serializzare un'entità.
Disattivazione per proprietà di navigazione specifiche
Il caricamento lento della raccolta Enrollments può essere disattivato rendendo la proprietà Enrollments non virtuale come illustrato nell'esempio seguente.
public partial class Student {
public Student() {
this.Enrollments = new HashSet<Enrollment>();
}
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public System.DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
Disattiva per tutte le entità
Il caricamento lento può essere disattivato per tutte le entità nel contesto impostando un flag sulla proprietà Configuration su false, come mostrato nell'esempio seguente.
public partial class UniContextEntities : DbContext {
public UniContextEntities(): base("name=UniContextEntities") {
this.Configuration.LazyLoadingEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
throw new UnintentionalCodeFirstException();
}
}
Dopo aver disattivato il caricamento lento, ora, quando esegui di nuovo l'esempio precedente, vedrai che le iscrizioni non vengono caricate e vengono recuperati solo i dati degli studenti.
ID: 1, Name: Ali Alexander
ID: 2, Name: Meredith Alons
ID: 3, Name: Arturo Anand
ID: 4, Name: Gytis Barzduka
ID: 5, Name: Yan Li
ID: 6, Name: Peggy Justice
ID: 7, Name: Laura Norman
ID: 8, Name: Nino Olivetto
Ti consigliamo di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
Quando hai disabilitato il caricamento lento, è ancora possibile caricare lentamente le entità correlate, ma deve essere fatto con una chiamata esplicita.
A differenza del caricamento lento, non vi è ambiguità o possibilità di confusione riguardo al momento in cui viene eseguita una query.
Per fare ciò si utilizza il metodo Load sulla voce dell'entità correlata.
Per una relazione uno-a-molti, chiama il metodo Load in Collection.
E per una relazione uno-a-uno, chiama il metodo Load su Riferimento.
Diamo un'occhiata al seguente esempio in cui il caricamento lento è disabilitato e quindi viene recuperato lo studente il cui nome è Ali.
Le informazioni sugli studenti vengono quindi scritte sulla console. Se si guarda il codice, vengono scritte anche le informazioni sulle registrazioni, ma l'entità Iscrizioni non è ancora caricata, quindi il ciclo foreach non verrà eseguito.
Dopo che l'entità Iscrizioni è stata caricata in modo esplicito, ora le informazioni sugli studenti e le informazioni sulle iscrizioni verranno scritte nella finestra della console.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
context.Configuration.LazyLoadingEnabled = false;
var student = (from s in context.Students where s.FirstMidName ==
"Ali" select s).FirstOrDefault<Student>();
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}", student.ID, name);
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0}, Course ID: {1}",
enrollment.EnrollmentID, enrollment.CourseID);
}
Console.WriteLine();
Console.WriteLine("Explicitly loaded Enrollments");
Console.WriteLine();
context.Entry(student).Collection(s ⇒ s.Enrollments).Load();
Console.WriteLine("ID: {0}, Name: {1}", student.ID, name);
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0}, Course ID: {1}",
enrollment.EnrollmentID, enrollment.CourseID);
}
Console.ReadKey();
}
}
}
Quando viene eseguito l'esempio precedente, riceverai il seguente output. Prima vengono visualizzate solo le informazioni sugli studenti e, dopo aver caricato esplicitamente l'entità delle iscrizioni, vengono visualizzate sia lo studente che le relative informazioni sulle iscrizioni.
ID: 1, Name: Ali Alexander
Explicitly loaded Enrollments
ID: 1, Name: Ali Alexander
Enrollment ID: 1, Course ID: 1050
Enrollment ID: 2, Course ID: 4022
Enrollment ID: 3, Course ID: 4041
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
In questo capitolo apprendiamo le tecniche di convalida che possono essere utilizzate in ADO.NET Entity Framework per convalidare i dati del modello. Entity Framework fornisce una grande varietà di funzionalità di convalida che possono essere implementate in un'interfaccia utente per la convalida lato client o possono essere utilizzate per la convalida lato server.
In Entity Framework, la convalida dei dati fa parte della soluzione per la cattura di dati non validi in un'applicazione.
Entity Framework convalida tutti i dati prima che vengano scritti nel database per impostazione predefinita, utilizzando un'ampia gamma di metodi di convalida dei dati.
Tuttavia, Entity Framework viene fornito dopo la convalida dei dati dell'interfaccia utente. Quindi, in quel caso, è necessaria la convalida dell'entità per gestire eventuali eccezioni generate da EF e mostrare un messaggio generico.
Esistono alcune tecniche di convalida dei dati per migliorare il controllo degli errori e come ritrasmettere i messaggi di errore all'utente.
DbContext ha un metodo Overridable denominato ValidateEntity. Quando chiami SaveChanges, Entity Framework chiamerà questo metodo per ogni entità nella sua cache il cui stato non è Unchanged. È possibile inserire la logica di convalida direttamente qui come mostrato nell'esempio seguente per l'entità studente.
public partial class UniContextEntities : DbContext {
protected override System.Data.Entity.Validation
.DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry,
System.Collections.Generic.IDictionary<object, object> items) {
if (entityEntry.Entity is Student) {
if (entityEntry.CurrentValues.GetValue<string>("FirstMidName") == "") {
var list = new List<System.Data.Entity
.Validation.DbValidationError>();
list.Add(new System.Data.Entity.Validation
.DbValidationError("FirstMidName", "FirstMidName is required"));
return new System.Data.Entity.Validation
.DbEntityValidationResult(entityEntry, list);
}
}
if (entityEntry.CurrentValues.GetValue<string>("LastName") == "") {
var list = new List<System.Data.Entity
.Validation.DbValidationError>();
list.Add(new System.Data.Entity.Validation
.DbValidationError("LastName", "LastName is required"));
return new System.Data.Entity.Validation
.DbEntityValidationResult(entityEntry, list);
}
return base.ValidateEntity(entityEntry, items);
}
}
Nel metodo ValidateEntity precedente, le proprietà FirstMidName e LastName dell'entità Student vengono controllate se una di queste proprietà ha una stringa vuota, quindi restituirà un messaggio di errore.
Diamo un'occhiata a un semplice esempio in cui viene creato un nuovo studente, ma il FirstMidName dello studente è una stringa vuota come mostrato nel codice seguente.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
Console.WriteLine("Adding new Student to the database");
Console.WriteLine();
try {
context.Students.Add(new Student() {
FirstMidName = "",
LastName = "Upston"
});
context.SaveChanges();
} catch (DbEntityValidationException dbValidationEx) {
foreach (DbEntityValidationResult entityErr in
dbValidationEx.EntityValidationErrors) {
foreach (DbValidationError error in entityErr.ValidationErrors) {
Console.WriteLine("Error: {0}",error.ErrorMessage);
}
}
}
Console.ReadKey();
}
}
}
Quando l'esempio precedente viene compilato ed eseguito, riceverai il seguente messaggio di errore nella finestra della console.
Adding new Student to the database
Error: FirstMidName is required
Ti consigliamo di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
Entity Framework fornisce la possibilità di tenere traccia delle modifiche apportate alle entità e alle loro relazioni, quindi gli aggiornamenti corretti vengono effettuati sul database quando viene chiamato il metodo di contesto SaveChanges. Questa è una caratteristica fondamentale di Entity Framework.
Il rilevamento delle modifiche tiene traccia delle modifiche durante l'aggiunta di nuovi record alla raccolta di entità, la modifica o la rimozione di entità esistenti.
Quindi tutte le modifiche vengono mantenute dal livello DbContext.
Queste modifiche alla traccia vengono perse se non vengono salvate prima che l'oggetto DbContext venga eliminato.
La classe DbChangeTracker fornisce tutte le informazioni sulle entità correnti tracciate dal contesto.
Per tenere traccia di qualsiasi entità in base al contesto, deve avere la proprietà della chiave primaria.
In Entity Framework, il rilevamento delle modifiche è abilitato per impostazione predefinita. È inoltre possibile disabilitare il rilevamento delle modifiche impostando la proprietà AutoDetectChangesEnabled di DbContext su false. Se questa proprietà è impostata su true, Entity Framework mantiene lo stato delle entità.
using (var context = new UniContextEntities()) {
context.Configuration.AutoDetectChangesEnabled = true;
}
Diamo uno sguardo al seguente esempio in cui gli studenti e le loro iscrizioni vengono recuperati dal database.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
context.Configuration.AutoDetectChangesEnabled = true;
Console.WriteLine("Retrieve Student");
var student = (from s in context.Students where s.FirstMidName ==
"Ali" select s).FirstOrDefault<Student>();
string name = student.FirstMidName + " " + student.LastName;
Console.WriteLine("ID: {0}, Name: {1}", student.ID, name);
Console.WriteLine();
Console.WriteLine("Retrieve all related enrollments");
foreach (var enrollment in student.Enrollments) {
Console.WriteLine("Enrollment ID: {0}, Course ID: {1}",
enrollment.EnrollmentID, enrollment.CourseID);
}
Console.WriteLine();
Console.WriteLine("Context tracking changes of {0} entity.",
context.ChangeTracker.Entries().Count());
var entries = context.ChangeTracker.Entries();
foreach (var entry in entries) {
Console.WriteLine("Entity Name: {0}", entry.Entity.GetType().Name);
Console.WriteLine("Status: {0}", entry.State);
}
Console.ReadKey();
}
}
}
Quando l'esempio precedente viene compilato ed eseguito, riceverai il seguente output.
Retrieve Student
ID: 1, Name: Ali Alexander
Retrieve all related enrollments
Enrollment ID: 1, Course ID: 1050
Enrollment ID: 2, Course ID: 4022
Enrollment ID: 3, Course ID: 4041
Context tracking changes of 4 entity.
Entity Name: Student
Status: Unchanged
Entity Name: Enrollment
Status: Unchanged
Entity Name: Enrollment
Status: Unchanged
Entity Name: Enrollment
Status: Unchanged
Puoi vedere che tutti i dati vengono recuperati solo dal database, ecco perché lo stato è invariato per tutte le entità.
Diamo ora un'occhiata a un altro semplice esempio in cui aggiungeremo un'altra iscrizione ed elimineremo uno studente dal database. Di seguito è riportato il codice in cui viene aggiunta la nuova iscrizione e uno studente viene eliminato.
class Program {
static void Main(string[] args) {
using (var context = new UniContextEntities()) {
context.Configuration.AutoDetectChangesEnabled = true;
Enrollment enr = new Enrollment() {
StudentID = 1, CourseID = 3141
};
Console.WriteLine("Adding New Enrollment");
context.Enrollments.Add(enr);
Console.WriteLine("Delete Student");
var student = (from s in context.Students where s.ID ==
23 select s).SingleOrDefault<Student>();
context.Students.Remove(student);
Console.WriteLine("");
Console.WriteLine("Context tracking changes of {0} entity.",
context.ChangeTracker.Entries().Count());
var entries = context.ChangeTracker.Entries();
foreach (var entry in entries) {
Console.WriteLine("Entity Name: {0}", entry.Entity.GetType().Name);
Console.WriteLine("Status: {0}", entry.State);
}
Console.ReadKey();
}
}
}
Quando l'esempio precedente viene compilato ed eseguito, riceverai il seguente output.
Adding New Enrollment
Delete Student
Context tracking changes of 2 entity.
Entity Name: Enrollment
Status: Added
Entity Name: Student
Status: Deleted
È ora possibile vedere che lo stato dell'entità di iscrizione è impostato su aggiunto e lo stato dell'entità studente viene eliminato, perché è stata aggiunta una nuova iscrizione e uno studente è stato rimosso dal database.
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
In Entity Framework, Colored Entity riguarda principalmente la modifica del colore dell'entità nella finestra di progettazione in modo che sia facile per gli sviluppatori identificare i gruppi di entità correlati nella finestra di progettazione di Visual Studio. Questa funzionalità è stata introdotta per la prima volta in Entity Framework 5.0.
Questa funzione non ha nulla a che fare con gli aspetti delle prestazioni.
Quando hai un progetto su larga scala e molte entità in un file edmx, questa funzione è molto utile per separare le tue entità in diversi moduli.
Se stai lavorando con il file edmx e l'hai aperto in designer, per cambiare il colore, seleziona un'entità nelle finestre di progettazione. Quindi fare clic con il tasto destro e selezionare Proprietà.
Nella finestra Proprietà selezionare la proprietà Colore di riempimento.
Specificare il colore utilizzando un nome di colore valido, ad esempio, Verde o un RGB valido (255, 128, 128) oppure è anche possibile selezionare dal selettore di colori.
Per cambiare il colore di più entità in una volta sola, seleziona più entità e modifica il colore di riempimento per tutte utilizzando la finestra delle proprietà.
È inoltre possibile modificare il formato delle proprietà selezionando una delle seguenti opzioni:
- Nome da visualizzare
- Visualizza nome e tipo
Per impostazione predefinita, l'opzione di visualizzazione del nome è selezionata. Per modificare il formato della proprietà, fare clic con il pulsante destro del mouse sulla finestra del designer.
Selezionare Formato proprietà scalare → Visualizza nome e tipo.
Ora puoi vedere che il tipo viene visualizzato anche insieme al nome.
Entity Framework fornisce tre approcci per creare un modello di entità e ognuno ha i propri pro e contro.
- Code First
- Database First
- Primo modello
In questo capitolo descriveremo brevemente il primo approccio al codice. Alcuni sviluppatori preferiscono lavorare con Designer in Code, mentre altri preferiscono lavorare semplicemente con il loro codice. Per questi sviluppatori, Entity Framework ha un flusso di lavoro di modellazione denominato Code First.
Il flusso di lavoro di modellazione Code First è destinato a un database che non esiste e Code First lo creerà.
Può essere utilizzato anche se si dispone di un database vuoto e quindi Code First aggiungerà nuove tabelle ad esso.
Code First consente di definire il modello utilizzando le classi C # o VB.Net.
Facoltativamente, è possibile eseguire una configurazione aggiuntiva utilizzando attributi nelle classi e nelle proprietà o utilizzando un'API fluente.
Perché Code First?
Code First è in realtà costituito da un insieme di pezzi del puzzle. Le prime sono le classi di dominio.
Le classi di dominio non hanno nulla a che fare con Entity Framework. Sono solo gli elementi del tuo dominio aziendale.
Entity Framework, quindi, ha un contesto che gestisce l'interazione tra quelle classi e il database.
Il contesto non è specifico di Code First. È una funzionalità di Entity Framework.
Code First aggiunge un generatore di modelli che ispeziona le classi gestite dal contesto, quindi utilizza una serie di regole o convenzioni per determinare come tali classi e relazioni descrivono un modello e come tale modello deve essere associato al database.
Tutto questo accade in fase di esecuzione. Non vedrai mai questo modello, è solo nella memoria.
Code First ha anche la capacità di utilizzare quel modello per creare un database, se lo desideri.
Può anche aggiornare il database se il modello cambia, utilizzando una funzionalità chiamata Code First Migrations.
Configurazione dell'ambiente
Per iniziare a lavorare con l'approccio EF Code First è necessario che i seguenti strumenti siano installati sul proprio sistema.
- Visual Studio 2013 (.net framework 4.5.2) o versione successiva.
- MS SQL Server 2012 o successivo.
- Entity Framework tramite NuGet Package.
Installa EF tramite il pacchetto NuGet
Step 1 - Innanzitutto, crea l'applicazione console da File → Nuovo → Progetto ...
Step 2 - Seleziona Windows dal riquadro di sinistra e Applicazione console dal riquadro dei modelli.
Step 3 - Inserisci EFCodeFirstDemo come nome e seleziona OK.
Step 4 - Fare clic con il pulsante destro del mouse sul progetto in Esplora soluzioni e selezionare Gestisci pacchetti NuGet ...
Questo aprirà NuGet Package Manager e cercherà EntityFramework. Questo cercherà tutti i pacchetti relativi a Entity Framework.
Step 5- Seleziona EntityFramework e fai clic su Installa. In alternativa, dal menu Strumenti fare clic su Gestione pacchetti NuGet e quindi su Console di Gestione pacchetti. Nella finestra della console di Gestione pacchetti, immetti il seguente comando: Install-Package EntityFramework.
Al termine dell'installazione, verrà visualizzato il seguente messaggio nella finestra di output "EntityFramework 6.1.2 installato con successo in EFCodeFirstDemo".
Dopo l'installazione, EntityFramework.dll verrà incluso nel progetto, come mostrato nell'immagine seguente.
Ora sei pronto per iniziare a lavorare sull'approccio Code First.
Definiamo un modello molto semplice utilizzando le classi. Li stiamo solo definendo nel file Program.cs ma in un'applicazione del mondo reale dividerete le vostre classi in file separati e potenzialmente in un progetto separato. Di seguito è riportato un modello di dati che creeremo utilizzando l'approccio Code First.
Crea modello
Aggiungere le tre classi seguenti nel file Program.cs utilizzando il codice seguente per la classe Student.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
La proprietà ID diventerà la colonna della chiave primaria della tabella del database che corrisponde a questa classe.
La proprietà Enrollments è una proprietà di navigazione. Le proprietà di navigazione contengono altre entità correlate a questa entità.
In questo caso, la proprietà Enrollments di un'entità Student conterrà tutte le entità Enrollment correlate a tale entità Student.
Le proprietà di navigazione sono in genere definite come virtuali in modo che possano sfruttare alcune funzionalità di Entity Framework come il caricamento lento.
Se una proprietà di navigazione può contenere più entità (come nelle relazioni molti-a-molti o uno-molti), il suo tipo deve essere un elenco in cui è possibile aggiungere, eliminare e aggiornare voci, come ICollection.
Di seguito è riportata l'implementazione per la classe Course.
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
La proprietà Enrollments è una proprietà di navigazione. Un'entità Corso può essere correlata a qualsiasi numero di entità Iscrizione.
Di seguito è riportata l'implementazione per la classe di registrazione e l'enumerazione.
public enum Grade {
A, B, C, D, F
}
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
La proprietà EnrollmentID sarà la chiave primaria.
La proprietà Grade è un'enumerazione. Il punto interrogativo dopo la dichiarazione del tipo Grade indica che la proprietà Grade è nullable.
Un voto nullo è diverso da un voto zero. Null significa che un voto non è noto o non è stato ancora assegnato.
Le proprietà StudentID e CourseID sono chiavi esterne e le proprietà di navigazione corrispondenti sono Student e Course.
Un'entità Enrollment è associata a un'entità Student e un'entità Course, quindi la proprietà può contenere solo un'entità Student e Course.
Crea contesto database
La classe principale che coordina la funzionalità di Entity Framework per un dato modello di dati è la classe del contesto del database che consente di interrogare e salvare i dati. È possibile creare questa classe derivando dalla classe DbContext ed esponendo un DbSet tipizzato
public class MyContext : DbContext {
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Di seguito è riportato il codice completo nel file Program.cs.
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFCodeFirstDemo {
class Program {
static void Main(string[] args) {}
}
public enum Grade {
A, B, C, D, F
}
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class MyContext : DbContext {
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
}
Il codice sopra è tutto ciò di cui abbiamo bisogno per iniziare a memorizzare e recuperare i dati. Aggiungiamo alcuni dati e poi recuperiamoli. Di seguito è riportato il codice nel metodo principale.
static void Main(string[] args) {
using (var context = new MyContext()) {
// Create and save a new Students
Console.WriteLine("Adding new students");
var student = new Student {
FirstMidName = "Alain", LastName = "Bomer",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
};
context.Students.Add(student);
var student1 = new Student {
FirstMidName = "Mark", LastName = "Upston",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
};
context.Students.Add(student1);
context.SaveChanges();
// Display all Students from the database
var students = (from s in context.Students
orderby s.FirstMidName select s).ToList<Student>();
Console.WriteLine("Retrieve all Students from the database:");
foreach (var stdnt in students) {
string name = stdnt.FirstMidName + " " + stdnt.LastName;
Console.WriteLine("ID: {0}, Name: {1}", stdnt.ID, name);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
Quando il codice precedente viene eseguito, riceverai il seguente output.
Adding new students
Retrieve all Students from the database:
ID: 1, Name: Alain Bomer
ID: 2, Name: Mark Upston
Press any key to exit...
Ora la domanda che mi viene in mente è: dove sono i dati e il database in cui abbiamo aggiunto alcuni dati e poi li abbiamo recuperati dal database. Per convenzione, DbContext ha creato un database per te.
Se è disponibile un'istanza SQL Express locale, Code First ha creato il database su tale istanza.
Se SQL Express non è disponibile, Code First proverà a utilizzare LocalDb.
Il database prende il nome dal nome completo del contesto derivato.
Nel nostro caso, l'istanza di SQL Express è disponibile e il nome del database è EFCodeFirstDemo.MyContext come mostrato nell'immagine seguente.
Queste sono solo le convenzioni predefinite e esistono diversi modi per modificare il database utilizzato da Code First.
Come puoi vedere nell'immagine sopra, ha creato tabelle Studenti, Corsi e Iscrizioni e ogni tabella contiene colonne con tipo di dati e lunghezza appropriati.
I nomi delle colonne e il tipo di dati corrispondono anche alle proprietà delle rispettive classi di dominio.
Inizializzazione del database
Nell'esempio sopra, abbiamo visto che Code First crea automaticamente un database, ma se vuoi cambiare il nome del database e del server, vediamo come Code First decide il nome del database e il server durante l'inizializzazione di un database. Dai un'occhiata al diagramma seguente.
È possibile definire il costruttore di base della classe di contesto nei seguenti modi.
- Nessun parametro
- Nome del database
- Nome stringa di connessione
Nessun parametro
Se si specifica il costruttore di base della classe di contesto senza alcun parametro come mostrato nell'esempio precedente, il framework di entità creerà un database nel server SQLEXPRESS locale con un nome {Namespace}. {Context class name}.
Nell'esempio precedente, il database che viene creato automaticamente ha il nome EFCodeFirstDemo.MyContext. Se guardi il nome, scoprirai che EFCodeFirstDemo è lo spazio dei nomi e MyContext è il nome della classe di contesto come mostrato nel codice seguente.
public class MyContext : DbContext {
public MyContext() : base() {}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Nome del database
Se si passa il nome del database come parametro in un costruttore di base della classe di contesto, Code First creerà di nuovo un database automaticamente, ma questa volta il nome sarà quello passato come parametro nel costruttore di base sul server di database SQLEXPRESS locale .
Nel codice seguente, MyContextDB viene specificato come parametro nel costruttore di base. Se esegui la tua applicazione, il database con il nome MyContextDB verrà creato nel tuo server SQL locale.
public class MyContext : DbContext {
public MyContext() : base("MyContextDB") {}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Nome stringa di connessione
Questo è un modo semplice per indicare a DbContext di utilizzare un server di database diverso da SQL Express o LocalDb. Puoi scegliere di inserire una stringa di connessione nel tuo file app.config.
Se il nome della stringa di connessione corrisponde al nome del contesto (con o senza la qualifica dello spazio dei nomi), verrà trovato da DbContext quando viene utilizzato il costruttore del parametro less.
Se il nome della stringa di connessione è diverso dal nome del contesto, è possibile indicare a DbContext di utilizzare questa connessione in modalità Code First passando il nome della stringa di connessione al costruttore DbContext.
public class MyContext : DbContext {
public MyContext() : base("name = MyContextDB") {}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Nel codice precedente, lo snippet della stringa di connessione della classe di contesto è specificato come parametro nel costruttore di base.
Il nome della stringa di connessione deve iniziare con "name =" altrimenti lo considererà come un nome di database.
Questo modulo rende esplicito che ti aspetti che la stringa di connessione venga trovata nel tuo file di configurazione. Verrà generata un'eccezione se non viene trovata una stringa di connessione con il nome specificato.
<connectionStrings>
<add name = "MyContextDB"
connectionString = "Data Source =.;Initial Catalog = EFMyContextDB;Integrated Security = true"
providerName = "System.Data.SqlClient"/>
</connectionStrings>
Il nome del database nella stringa di connessione in app.config è EFMyContextDB. CodeFirst creerà un nuovo fileEFMyContextDB database o usa esistente EFMyContextDB database in SQL Server locale.
Classi di dominio
Finora abbiamo lasciato che EF scoprisse il modello usando le sue convenzioni predefinite, ma ci saranno momenti in cui le nostre classi non seguono le convenzioni e dobbiamo essere in grado di eseguire ulteriori configurazioni. Ma puoi ignorare queste convenzioni configurando le classi di dominio per fornire a EF le informazioni di cui ha bisogno. Sono disponibili due opzioni per configurare le classi di dominio:
- Annotazioni dei dati
- API fluente
Annotazioni dei dati
DataAnnotations viene utilizzato per configurare le classi che evidenzieranno le configurazioni più comunemente necessarie. DataAnnotations sono comprese anche da una serie di applicazioni .NET, come ASP.NET MVC, che consentono a queste applicazioni di sfruttare le stesse annotazioni per le convalide lato client.
Di seguito sono riportate le annotazioni dei dati utilizzate nella classe dello studente.
public class Enrollment {
[Key]
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
[ForeignKey("CourseID")]
public virtual Course Course { get; set; }
[ForeignKey("ID")]
public virtual Student Student { get; set; }
}
API fluente
La maggior parte della configurazione del modello può essere eseguita utilizzando semplici annotazioni dei dati. L'API fluente è un modo avanzato per specificare la configurazione del modello che copre tutto ciò che le annotazioni dei dati possono fare, oltre ad alcune configurazioni più avanzate non possibili con le annotazioni dei dati. Le annotazioni dei dati e l'API fluente possono essere utilizzate insieme.
Per accedere all'API fluente, sovrascrivi il metodo OnModelCreating in DbContext. Ora rinominiamo il nome della colonna nella tabella degli studenti da FirstMidName a FirstName come mostrato nel codice seguente.
public class MyContext : DbContext {
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
.HasColumnName("FirstName");
}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
DataAnnotations viene utilizzato per configurare le classi che evidenzieranno le configurazioni più comunemente necessarie. DataAnnotations sono comprese anche da numerose applicazioni .NET, come ASP.NET MVC, che consente a queste applicazioni di sfruttare le stesse annotazioni per le convalide lato client. Gli attributi DataAnnotation sovrascrivono le convenzioni CodeFirst predefinite.
System.ComponentModel.DataAnnotations include i seguenti attributi che influiscono sul nullability o sulla dimensione della colonna.
- Key
- Timestamp
- ConcurrencyCheck
- Required
- MinLength
- MaxLength
- StringLength
System.ComponentModel.DataAnnotations.Schema lo spazio dei nomi include i seguenti attributi che influiscono sullo schema del database.
- Table
- Column
- Index
- ForeignKey
- NotMapped
- InverseProperty
Chiave
Entity Framework si basa su ogni entità che ha un valore chiave che usa per tenere traccia delle entità. Una delle convenzioni da cui dipende Code First è il modo in cui implica quale proprietà è la chiave in ciascuna delle classi Code First.
La convenzione consiste nel cercare una proprietà denominata "Id" o una che combini il nome della classe e "Id", ad esempio "StudentId".
La proprietà verrà mappata a una colonna chiave primaria nel database.
Le classi Studente, Corso e Iscrizione seguono questa convenzione.
Supponiamo ora che la classe Student abbia utilizzato il nome StdntID invece di ID. Quando Code First non trova una proprietà che corrisponde a questa convenzione, verrà generata un'eccezione a causa del requisito di Entity Framework che richiede una proprietà chiave. È possibile utilizzare l'annotazione chiave per specificare quale proprietà deve essere utilizzata come EntityKey.
Diamo un'occhiata al seguente codice di una classe Student che contiene StdntID, ma non segue la convenzione Code First predefinita. Quindi, per gestire questo, viene aggiunto un attributo Key che lo renderà una chiave primaria.
public class Student {
[Key]
public int StdntID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Quando si esegue l'applicazione e si guarda nel database in SQL Server Explorer, si vedrà che la chiave primaria è ora StdntID nella tabella Studenti.
Entity Framework supporta anche chiavi composite. Composite keyssono anche chiavi primarie costituite da più di una proprietà. Ad esempio, hai una classe DrivingLicense la cui chiave primaria è una combinazione di LicenseNumber e IssuingCountry.
public class DrivingLicense {
[Key, Column(Order = 1)]
public int LicenseNumber { get; set; }
[Key, Column(Order = 2)]
public string IssuingCountry { get; set; }
public DateTime Issued { get; set; }
public DateTime Expires { get; set; }
}
Quando si dispone di chiavi composite, Entity Framework richiede di definire un ordine delle proprietà della chiave. Puoi farlo utilizzando l'annotazione Colonna per specificare un ordine.
Timestamp
Code First tratterà le proprietà Timestamp come le proprietà ConcurrencyCheck, ma garantirà anche che il campo del database che il codice genera per primo non sia annullabile.
È più comune utilizzare i campi rowversion o timestamp per il controllo della concorrenza.
Anziché utilizzare l'annotazione ConcurrencyCheck, è possibile utilizzare l'annotazione TimeStamp più specifica purché il tipo della proprietà sia un array di byte.
Puoi avere solo una proprietà timestamp in una data classe.
Diamo un'occhiata a un semplice esempio aggiungendo la proprietà TimeStamp alla classe Course:
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
[Timestamp]
public byte[] TStamp { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Come puoi vedere nell'esempio sopra, l'attributo Timestamp viene applicato alla proprietà Byte [] della classe Course. Quindi, Code First creerà una colonna timestamp TStamp
nella tabella Courses.
ConcurrencyCheck
L'annotazione ConcurrencyCheck consente di contrassegnare una o più proprietà da utilizzare per il controllo della concorrenza nel database quando un utente modifica o elimina un'entità. Se hai lavorato con EF Designer, questo è allineato con l'impostazione di ConcurrencyMode di una proprietà su Fixed.
Diamo un'occhiata a un semplice esempio di come funziona ConcurrencyCheck aggiungendolo alla proprietà Title nella classe Course.
public class Course {
public int CourseID { get; set; }
[ConcurrencyCheck]
public string Title { get; set; }
public int Credits { get; set; }
[Timestamp, DataType("timestamp")]
public byte[] TimeStamp { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Nella classe Course precedente, l'attributo ConcurrencyCheck viene applicato alla proprietà Title esistente. Ora, Code First includerà la colonna Title nel comando di aggiornamento per verificare la concorrenza ottimistica, come mostrato nel codice seguente.
exec sp_executesql N'UPDATE [dbo].[Courses]
SET [Title] = @0
WHERE (([CourseID] = @1) AND ([Title] = @2))
',N'@0 nvarchar(max) ,@1 int,@2 nvarchar(max) ',@0=N'Maths',@1=1,@2=N'Calculus'
go
Annotazione richiesta
L'annotazione Required indica a EF che è richiesta una particolare proprietà. Diamo un'occhiata alla seguente classe Student in cui l'ID richiesto viene aggiunto alla proprietà FirstMidName. L'attributo obbligatorio costringerà EF a garantire che la proprietà contenga dati.
public class Student {
[Key]
public int StdntID { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Come si vede nell'esempio precedente, l'attributo Required viene applicato a FirstMidName e LastName. Quindi, Code First creerà le colonne FirstMidName e LastName NOT NULL nella tabella Studenti come mostrato nell'immagine seguente.
Lunghezza massima
L'attributo MaxLength consente di specificare ulteriori convalide di proprietà. Può essere applicato a una proprietà di tipo stringa o matrice di una classe di dominio. EF Code First imposterà la dimensione di una colonna come specificato nell'attributo MaxLength.
Diamo un'occhiata alla seguente classe Course in cui l'attributo MaxLength (24) è applicato alla proprietà Title.
public class Course {
public int CourseID { get; set; }
[ConcurrencyCheck]
[MaxLength(24)]
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Quando si esegue l'applicazione precedente, Code First creerà un titolo di colonna nvarchar (24) nella tabella CourseId come mostrato nell'immagine seguente.
Quando l'utente imposta il titolo che contiene più di 24 caratteri, EF lancerà EntityValidationError.
MinLength
L'attributo MinLength ti consente inoltre di specificare ulteriori convalide di proprietà, proprio come hai fatto con MaxLength. L'attributo MinLength può essere utilizzato anche con l'attributo MaxLength come mostrato nel codice seguente.
public class Course {
public int CourseID { get; set; }
[ConcurrencyCheck]
[MaxLength(24) , MinLength(5)]
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
EF genererà EntityValidationError, se imposti un valore della proprietà Title inferiore alla lunghezza specificata nell'attributo MinLength o maggiore della lunghezza specificata nell'attributo MaxLength.
StringLength
StringLength consente inoltre di specificare convalide di proprietà aggiuntive come MaxLength. L'unica differenza è che l'attributo StringLength può essere applicato solo a una proprietà di tipo stringa delle classi di dominio.
public class Course {
public int CourseID { get; set; }
[StringLength (24)]
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Entity Framework convalida anche il valore di una proprietà per l'attributo StringLength. Se l'utente imposta il titolo che contiene più di 24 caratteri, EF lancerà EntityValidationError.
tavolo
La convenzione Code First predefinita crea un nome di tabella simile al nome della classe. Se si lascia che Code First crei il database e si desidera anche modificare il nome delle tabelle che sta creando. Quindi -
È possibile utilizzare Code First con un database esistente. Ma non è sempre il caso che i nomi delle classi corrispondano ai nomi delle tabelle nel database.
L'attributo di tabella sovrascrive questa convenzione predefinita.
EF Code First creerà una tabella con un nome specificato nell'attributo Table per una determinata classe di dominio.
Diamo un'occhiata al seguente esempio in cui la classe è denominata Student e per convenzione, Code First presume che verrà mappata a una tabella denominata Students. In caso contrario, è possibile specificare il nome della tabella con l'attributo Table come mostrato nel codice seguente.
[Table("StudentsInfo")]
public class Student {
[Key]
public int StdntID { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Ora puoi vedere che l'attributo Table specifica la tabella come StudentsInfo. Quando la tabella viene generata, vedrai il nome della tabella StudentsInfo come mostrato nell'immagine seguente.
Non è possibile specificare solo il nome della tabella, ma è anche possibile specificare uno schema per la tabella utilizzando l'attributo Table, come mostrato nel codice seguente.
[Table("StudentsInfo", Schema = "Admin")]
public class Student {
[Key]
public int StdntID { get; set; }
[Required]
public string LastName { get; set; }
[Required]
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Puoi vedere nell'esempio sopra, la tabella è specificata con lo schema admin. Ora Code First creerà la tabella StudentsInfo nello schema Admin come mostrato nell'immagine seguente.
Colonna
È anche lo stesso dell'attributo Table, ma l'attributo Table sostituisce il comportamento della tabella mentre l'attributo Column sostituisce il comportamento della colonna. La convenzione Code First predefinita crea un nome di colonna simile al nome della proprietà. Se si lascia che Code First crei il database e si desidera anche modificare il nome delle colonne nelle tabelle. Quindi -
L'attributo di colonna sovrascrive la convenzione predefinita.
EF Code First creerà una colonna con un nome specificato nell'attributo Column per una determinata proprietà.
Diamo un'occhiata al seguente esempio in cui la proprietà è denominata FirstMidName e per convenzione, Code First presume che questo verrà mappato a una colonna denominata FirstMidName.
In caso contrario, puoi specificare il nome della colonna con l'attributo Column come mostrato nel codice seguente.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
[Column("FirstName")]
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Puoi vedere che l'attributo Column specifica la colonna come FirstName. Quando viene generata la tabella, vedrai il nome della colonna FirstName come mostrato nell'immagine seguente.
Indice
L'attributo Index è stato introdotto in Entity Framework 6.1. Se stai utilizzando una versione precedente, le informazioni in questa sezione non si applicano.
È possibile creare un indice su una o più colonne utilizzando IndexAttribute.
L'aggiunta dell'attributo a una o più proprietà farà sì che EF crei l'indice corrispondente nel database quando crea il database.
Gli indici rendono il recupero dei dati più veloce ed efficiente, nella maggior parte dei casi. Tuttavia, il sovraccarico di una tabella o una vista con gli indici potrebbe influire in modo spiacevole sulle prestazioni di altre operazioni come inserimenti o aggiornamenti.
L'indicizzazione è la nuova funzionalità di Entity Framework in cui è possibile migliorare le prestazioni dell'applicazione Code First riducendo il tempo necessario per eseguire query sui dati dal database.
È possibile aggiungere indici al database utilizzando l'attributo Index e sovrascrivere le impostazioni Univoche e Cluster predefinite per ottenere l'indice più adatto al proprio scenario.
Per impostazione predefinita, l'indice verrà denominato IX_ <nome proprietà>
Diamo un'occhiata al codice seguente in cui viene aggiunto l'attributo Index nella classe del corso per i crediti.
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
[Index]
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Puoi vedere che l'attributo Index viene applicato alla proprietà Credits. Quando la tabella viene generata, vedrai IX_Credits negli indici.
Per impostazione predefinita, gli indici non sono univoci, ma puoi utilizzare l'estensione IsUniqueparametro denominato per specificare che un indice deve essere univoco. L'esempio seguente introduce un indice univoco come illustrato nel codice seguente.
public class Course {
public int CourseID { get; set; }
[Index(IsUnique = true)]
public string Title { get; set; }
[Index]
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
ForeignKey
La convenzione Code First si prenderà cura delle relazioni più comuni nel tuo modello, ma in alcuni casi è necessaria assistenza. Ad esempio, la modifica del nome della proprietà della chiave nella classe Student ha creato un problema con la sua relazione con la classe Enrollment.
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
[Key]
public int StdntID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Durante la generazione del database, Code First vede la proprietà StudentID nella classe Enrollment e la riconosce, per convenzione, che corrisponde a un nome di classe più "ID", come chiave esterna per la classe Student. Tuttavia, non esiste una proprietà StudentID nella classe Student, ma la proprietà StdntID è la classe Student.
La soluzione per questo è creare una proprietà di navigazione in Enrollment e usare ForeignKey DataAnnotation per aiutare Code First a capire come costruire la relazione tra le due classi come mostrato nel codice seguente.
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
[ForeignKey("StudentID")]
public virtual Student Student { get; set; }
}
È ora possibile vedere che l'attributo ForeignKey è applicato alla proprietà di navigazione.
NotMapped
Per le convenzioni predefinite di Code First, ogni proprietà che è di un tipo di dati supportato e che include getter e setter è rappresentata nel database. Ma questo non è sempre il caso delle tue applicazioni. L'attributo NotMapped sovrascrive questa convenzione predefinita. Ad esempio, potresti avere una proprietà nella classe Student come FatherName, ma non è necessario memorizzarla. È possibile applicare l'attributo NotMapped a una proprietà FatherName di cui non si desidera creare una colonna nel database, come mostrato nel codice seguente.
public class Student {
[Key]
public int StdntID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
[NotMapped]
public int FatherName { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
È possibile vedere che l'attributo NotMapped viene applicato alla proprietà FatherName. Quando la tabella viene generata vedrai che la colonna FatherName non verrà creata in un database, ma è presente nella classe Student.
Code First non creerà una colonna per una proprietà che non ha getter o setter come mostrato nell'esempio seguente delle proprietà Address ed Age della classe Student.
InverseProperty
InverseProperty viene utilizzato quando si hanno più relazioni tra le classi. Nel corso di iscrizione, potresti voler tenere traccia di chi si è iscritto a un corso corrente e a un corso precedente. Aggiungiamo due proprietà di navigazione per la classe Enrollment.
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course CurrCourse { get; set; }
public virtual Course PrevCourse { get; set; }
public virtual Student Student { get; set; }
}
Allo stesso modo, dovrai anche aggiungere nella classe Course a cui fanno riferimento queste proprietà. La classe Course ha proprietà di navigazione per tornare alla classe Enrollment, che contiene tutte le iscrizioni correnti e precedenti.
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
[Index]
public int Credits { get; set; }
public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}
Code First crea la colonna della chiave esterna {Nome classe} _ {Chiave primaria}, se la proprietà della chiave esterna non è inclusa in una particolare classe come mostrato nelle classi precedenti. Quando il database viene generato, vedrai le seguenti chiavi esterne.
Come puoi vedere, Code first non è in grado di abbinare le proprietà nelle due classi da solo. La tabella del database per le iscrizioni dovrebbe avere una chiave esterna per CurrCourse e una per PrevCourse, ma Code First creerà quattro proprietà della chiave esterna, ad es.
- CurrCourse _CourseID
- PrevCourse _CourseID
- Course_CourseID e
- Course_CourseID1
Per risolvere questi problemi, è possibile utilizzare l'annotazione InverseProperty per specificare l'allineamento delle proprietà.
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
[Index]
public int Credits { get; set; }
[InverseProperty("CurrCourse")]
public virtual ICollection<Enrollment> CurrEnrollments { get; set; }
[InverseProperty("PrevCourse")]
public virtual ICollection<Enrollment> PrevEnrollments { get; set; }
}
Come puoi vedere l'attributo InverseProperty viene applicato nella classe Course sopra specificando a quale proprietà di riferimento della classe Enrollment appartiene. Ora, Code First genererà un database e creerà solo due colonne di chiavi esterne nella tabella Iscrizioni come mostrato nell'immagine seguente.
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
L'API fluente è un modo avanzato per specificare la configurazione del modello che copre tutto ciò che le annotazioni dei dati possono fare oltre a una configurazione più avanzata non possibile con le annotazioni dei dati. Le annotazioni dei dati e l'API fluente possono essere utilizzate insieme, ma Code First dà la precedenza a API fluente> annotazioni dei dati> convenzioni predefinite.
L'API fluente è un altro modo per configurare le classi di dominio.
L'API Code First Fluent è più comunemente accessibile sovrascrivendo il metodo OnModelCreating nel tuo DbContext derivato.
L'API Fluent fornisce più funzionalità per la configurazione rispetto a DataAnnotations. L'API Fluent supporta i seguenti tipi di mapping.
In questo capitolo, continueremo con il semplice esempio che contiene le classi Student, Course e Enrollment e una classe di contesto con il nome MyContext come mostrato nel codice seguente.
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFCodeFirstDemo {
class Program {
static void Main(string[] args) {}
}
public enum Grade {
A, B, C, D, F
}
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class MyContext : DbContext {
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
}
Per accedere all'API Fluent è necessario eseguire l'override del metodo OnModelCreating in DbContext. Diamo un'occhiata a un semplice esempio in cui rinomineremo il nome della colonna nella tabella degli studenti da FirstMidName a FirstName come mostrato nel codice seguente.
public class MyContext : DbContext {
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
.HasColumnName("FirstName");}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
DbModelBuilder viene utilizzato per mappare le classi CLR a uno schema di database. È la classe principale e su cui puoi configurare tutte le tue classi di dominio. Questo approccio incentrato sul codice alla creazione di un Entity Data Model (EDM) è noto come Code First.
L'API Fluent fornisce una serie di metodi importanti per configurare le entità e le sue proprietà per sovrascrivere varie convenzioni Code First. Di seguito sono riportati alcuni di loro.
Sr. No. | Nome e descrizione del metodo |
---|---|
1 | ComplexType<TComplexType> Registra un tipo come tipo complesso nel modello e restituisce un oggetto che può essere utilizzato per configurare il tipo complesso. Questo metodo può essere chiamato più volte affinché lo stesso tipo esegua più righe di configurazione. |
2 | Entity<TEntityType> Registra un tipo di entità come parte del modello e restituisce un oggetto che può essere utilizzato per configurare l'entità. Questo metodo può essere chiamato più volte affinché la stessa entità esegua più righe di configurazione. |
3 | HasKey<TKey> Configura le proprietà della chiave primaria per questo tipo di entità. |
4 | HasMany<TTargetEntity> Configura una relazione a molti da questo tipo di entità. |
5 | HasOptional<TTargetEntity> Configura una relazione facoltativa da questo tipo di entità. Le istanze del tipo di entità potranno essere salvate nel database senza che venga specificata questa relazione. La chiave esterna nel database sarà nullable. |
6 | HasRequired<TTargetEntity> Configura una relazione richiesta da questo tipo di entità. Le istanze del tipo di entità non potranno essere salvate nel database a meno che non venga specificata questa relazione. La chiave esterna nel database sarà non annullabile. |
7 | Ignore<TProperty> Esclude una proprietà dal modello in modo che non venga mappata al database. (Ereditato da StructuralTypeConfiguration <TStructuralType>) |
8 | Property<T> Configura una proprietà struct definita su questo tipo. (Ereditato da StructuralTypeConfiguration <TStructuralType>) |
9 | ToTable(String) Configura il nome della tabella a cui è mappato questo tipo di entità. |
L'API fluente ti consente di configurare le tue entità o le loro proprietà, sia che tu voglia cambiare qualcosa sul modo in cui si associano al database o su come si relazionano tra loro. Esiste un'enorme varietà di mappature e modelli su cui puoi influire utilizzando le configurazioni. Di seguito sono riportati i principali tipi di mappatura supportati da Fluent API:
- Mappatura entità
- Mappatura delle proprietà
Mappatura entità
Il mapping di entità è solo alcuni semplici mapping che influiranno sulla comprensione di Entity Framework di come le classi vengono mappate ai database. Di tutto ciò abbiamo discusso nelle annotazioni dei dati e qui vedremo come ottenere le stesse cose utilizzando Fluent API.
Quindi, invece di entrare nelle classi di dominio per aggiungere queste configurazioni, possiamo farlo all'interno del contesto.
La prima cosa è sovrascrivere il metodo OnModelCreating, che consente a modelBuilder di lavorare.
Schema predefinito
Lo schema predefinito è dbo quando viene generato il database. È possibile utilizzare il metodo HasDefaultSchema su DbModelBuilder per specificare lo schema del database da utilizzare per tutte le tabelle, le stored procedure, ecc.
Diamo un'occhiata al seguente esempio in cui viene applicato lo schema di amministrazione.
public class MyContext : DbContext {
public MyContext() : base("name = MyContextDB") {}
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Mappa entità su tabella
Con la convenzione predefinita, Code First creerà le tabelle del database con il nome delle proprietà DbSet nella classe di contesto come Courses, Enrollments e Students. Ma se si vogliono nomi di tabella diversi, è possibile ignorare questa convenzione e fornire un nome di tabella diverso rispetto alle proprietà DbSet, come illustrato nel codice seguente.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
//Map entity to table
modelBuilder.Entity<Student>().ToTable("StudentData");
modelBuilder.Entity<Course>().ToTable("CourseDetail");
modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo");
}
Quando il database viene generato, vedrai il nome delle tabelle come specificato nel metodo OnModelCreating.
Divisione entità (mappa entità su più tabelle)
Entity Splitting ti consente di combinare i dati provenienti da più tabelle in una singola classe e può essere utilizzato solo con tabelle che hanno una relazione uno-a-uno tra di loro. Diamo un'occhiata al seguente esempio in cui le informazioni sugli studenti sono mappate in due tabelle.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
//Map entity to table
modelBuilder.Entity<Student>().Map(sd ⇒ {
sd.Properties(p ⇒ new { p.ID, p.FirstMidName, p.LastName });
sd.ToTable("StudentData");
})
.Map(si ⇒ {
si.Properties(p ⇒ new { p.ID, p.EnrollmentDate });
si.ToTable("StudentEnrollmentInfo");
});
modelBuilder.Entity<Course>().ToTable("CourseDetail");
modelBuilder.Entity<Enrollment>().ToTable("EnrollmentInfo");
}
Nel codice precedente, puoi vedere che l'entità Student è suddivisa nelle due tabelle seguenti mappando alcune proprietà alla tabella StudentData e alcune proprietà alla tabella StudentEnrollmentInfo utilizzando il metodo Map.
StudentData - Contiene Student FirstMidName e Last Name.
StudentEnrollmentInfo - Contiene EnrollmentDate.
Quando il database viene generato, vengono visualizzate le seguenti tabelle nel database, come mostrato nell'immagine seguente.
Mappatura delle proprietà
Il metodo Property viene utilizzato per configurare gli attributi per ciascuna proprietà appartenente a un'entità o un tipo complesso. Il metodo Property viene utilizzato per ottenere un oggetto di configurazione per una determinata proprietà. Puoi anche mappare e configurare le proprietà delle tue classi di dominio utilizzando Fluent API.
Configurazione di una chiave primaria
La convenzione predefinita per le chiavi primarie è:
- La classe definisce una proprietà il cui nome è "ID" o "Id"
- Nome della classe seguito da "ID" o "ID"
Se la tua classe non segue le convenzioni predefinite per la chiave primaria come mostrato nel seguente codice della classe Student:
public class Student {
public int StdntID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Quindi, per impostare esplicitamente una proprietà come chiave primaria, è possibile utilizzare il metodo HasKey come mostrato nel codice seguente:
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure Primary Key
modelBuilder.Entity<Student>().HasKey<int>(s ⇒ s.StdntID);
}
Configura colonna
In Entity Framework, per impostazione predefinita Code First creerà una colonna per una proprietà con lo stesso nome, ordine e tipo di dati. Ma puoi anche ignorare questa convenzione, come mostrato nel codice seguente.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
//Configure EnrollmentDate Column
modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate)
.HasColumnName("EnDate")
.HasColumnType("DateTime")
.HasColumnOrder(2);
}
Configura proprietà MaxLength
Nell'esempio seguente, la proprietà Titolo del corso non dovrebbe contenere più di 24 caratteri. Quando l'utente specifica un valore più lungo di 24 caratteri, l'utente riceverà un'eccezione DbEntityValidationException.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
modelBuilder.Entity<Course>().Property(p ⇒ p.Title).HasMaxLength(24);
}
Configurare la proprietà Null o NotNull
Nell'esempio seguente, la proprietà Titolo del corso è obbligatoria, quindi il metodo IsRequired viene utilizzato per creare la colonna NotNull. Allo stesso modo, Student EnrollmentDate è facoltativo, quindi useremo il metodo IsOptional per consentire un valore null in questa colonna, come mostrato nel codice seguente.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
modelBuilder.Entity<Course>().Property(p ⇒ p.Title).IsRequired();
modelBuilder.Entity<Student>().Property(p ⇒ p.EnrollmentDate).IsOptional();
//modelBuilder.Entity<Student>().Property(s ⇒ s.FirstMidName)
//.HasColumnName("FirstName");
}
Configurazione delle relazioni
Una relazione, nel contesto dei database, è una situazione che esiste tra due tabelle di database relazionali, quando una tabella ha una chiave esterna che fa riferimento alla chiave primaria dell'altra tabella. Quando si lavora con Code First, si definisce il modello definendo le classi CLR del dominio. Per impostazione predefinita, Entity Framework usa le convenzioni Code First per mappare le classi allo schema del database.
Se si utilizzano le convenzioni di denominazione Code First, nella maggior parte dei casi è possibile fare affidamento su Code First per impostare le relazioni tra le tabelle in base alle chiavi esterne e alle proprietà di navigazione.
Se non soddisfano queste convenzioni, ci sono anche configurazioni che puoi usare per influenzare le relazioni tra le classi e come queste relazioni vengono realizzate nel database quando aggiungi configurazioni in Code First.
Alcuni di essi sono disponibili nelle annotazioni dei dati e puoi applicarne altri ancora più complicati con un'API Fluent.
Configurare la relazione uno a uno
Quando si definisce una relazione uno-a-uno nel modello, si utilizza una proprietà di navigazione di riferimento in ogni classe. Nel database, entrambe le tabelle possono avere un solo record su entrambi i lati della relazione. Ogni valore di chiave primaria si riferisce a un solo record (o nessun record) nella tabella correlata.
Viene creata una relazione uno-a-uno se entrambe le colonne correlate sono chiavi primarie o hanno vincoli univoci.
In una relazione uno a uno, la chiave primaria funge inoltre da chiave esterna e non esiste una colonna di chiave esterna separata per nessuna delle due tabelle.
Questo tipo di relazione non è comune perché la maggior parte delle informazioni correlate in questo modo sarebbero tutte in una tabella.
Diamo un'occhiata al seguente esempio in cui aggiungeremo un'altra classe al nostro modello per creare una relazione uno-a-uno.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual StudentLogIn StudentLogIn { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class StudentLogIn {
[Key, ForeignKey("Student")]
public int ID { get; set; }
public string EmailID { get; set; }
public string Password { get; set; }
public virtual Student Student { get; set; }
}
Come puoi vedere nel codice sopra, gli attributi Key e ForeignKey vengono utilizzati per la proprietà ID nella classe StudentLogIn, al fine di contrassegnarla come chiave primaria oltre che come chiave esterna.
Per configurare una relazione da uno a zero o una relazione tra Student e StudentLogIn utilizzando l'API Fluent, è necessario eseguire l'override del metodo OnModelCreating come illustrato nel codice seguente.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure ID as PK for StudentLogIn
modelBuilder.Entity<StudentLogIn>()
.HasKey(s ⇒ s.ID);
// Configure ID as FK for StudentLogIn
modelBuilder.Entity<Student>()
.HasOptional(s ⇒ s.StudentLogIn) //StudentLogIn is optional
.WithRequired(t ⇒ t.Student); // Create inverse relationship
}
Nella maggior parte dei casi, Entity Framework può dedurre quale tipo è dipendente e quale è l'entità in una relazione. Tuttavia, quando sono richieste entrambe le estremità della relazione o entrambe le parti sono facoltative, Entity Framework non può identificare il dipendente e il principale. Quando sono necessarie entrambe le estremità della relazione, è possibile usare HasRequired come illustrato nel codice seguente.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure ID as PK for StudentLogIn
modelBuilder.Entity<StudentLogIn>()
.HasKey(s ⇒ s.ID);
// Configure ID as FK for StudentLogIn
modelBuilder.Entity<Student>()
.HasRequired(r ⇒ r.Student)
.WithOptional(s ⇒ s.StudentLogIn);
}
Quando il database viene generato, vedrai che la relazione viene creata come mostrato nell'immagine seguente.
Configurare la relazione uno-a-molti
La tabella della chiave primaria contiene un solo record che non si riferisce a nessuno, uno o più record nella tabella correlata. Questo è il tipo di relazione più comunemente usato.
In questo tipo di relazione, una riga nella tabella A può avere molte righe corrispondenti nella tabella B, ma una riga nella tabella B può avere solo una riga corrispondente nella tabella A.
La chiave esterna è definita nella tabella che rappresenta le molte estremità della relazione.
Ad esempio, nel diagramma precedente le tabelle Studente e Iscrizione hanno una relazione da uno a più, ogni studente può avere molte iscrizioni, ma ciascuna iscrizione appartiene a un solo studente.
Di seguito sono riportati lo studente e l'iscrizione che hanno una relazione uno-a-molti, ma la chiave esterna nella tabella di iscrizione non segue le convenzioni Code First predefinite.
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
//StdntID is not following code first conventions name
public int StdntID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual StudentLogIn StudentLogIn { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
In questo caso, per configurare una relazione uno-a-molti utilizzando l'API Fluent, è necessario utilizzare il metodo HasForeignKey come mostrato nel codice seguente.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
//Configure FK for one-to-many relationship
modelBuilder.Entity<Enrollment>()
.HasRequired<Student>(s ⇒ s.Student)
.WithMany(t ⇒ t.Enrollments)
.HasForeignKey(u ⇒ u.StdntID);
}
Quando il database viene generato, vedrai che la relazione viene creata come mostrato nell'immagine seguente.
Nell'esempio precedente, il metodo HasRequired specifica che la proprietà di navigazione Student deve essere Null. Quindi è necessario assegnare a Studente l'entità Iscrizione ogni volta che si aggiunge o si aggiorna l'iscrizione. Per gestire ciò, è necessario utilizzare il metodo HasOptional invece del metodo HasRequired.
Configura relazione molti-a-molti
Ciascun record in entrambe le tabelle può essere correlato a un numero qualsiasi di record (o nessun record) nell'altra tabella.
È possibile creare tale relazione definendo una terza tabella, chiamata tabella di giunzione, la cui chiave primaria è costituita dalle chiavi esterne sia della tabella A che della tabella B.
Ad esempio, la tabella Studente e la tabella Corso hanno una relazione molti-a-molti.
Di seguito sono riportate le classi Studente e Corso in cui Studente e Corso hanno una relazione molti-molti, perché entrambe le classi hanno proprietà di navigazione Studenti e Corsi che sono raccolte. In altre parole, un'entità ha un'altra raccolta di entità.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Course> Courses { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Per configurare la relazione molti-a-molti tra Studente e Corso, puoi usare l'API Fluent come mostrato nel codice seguente.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure many-to-many relationship
modelBuilder.Entity<Student>()
.HasMany(s ⇒ s.Courses)
.WithMany(s ⇒ s.Students);
}
Le convenzioni Code First predefinite vengono utilizzate per creare una tabella di join quando viene generato il database. Di conseguenza, la tabella StudentCourses viene creata con le colonne Course_CourseID e Student_ID come mostrato nell'immagine seguente.
Se si desidera specificare il nome della tabella di join ei nomi delle colonne nella tabella è necessario eseguire una configurazione aggiuntiva utilizzando il metodo Map.
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
//Configure default schema
modelBuilder.HasDefaultSchema("Admin");
// Configure many-to-many relationship
modelBuilder.Entity<Student>()
.HasMany(s ⇒ s.Courses)
.WithMany(s ⇒ s.Students)
.Map(m ⇒ {
m.ToTable("StudentCoursesTable");
m.MapLeftKey("StudentID");
m.MapRightKey("CourseID");
});
}
È possibile vedere quando viene generato il database, il nome della tabella e delle colonne viene creato come specificato nel codice sopra.
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
In Entity Framework, Seed è stato introdotto in EF 4.1 e funziona con gli inizializzatori di database. L'idea generale di un fileSeed Methodconsiste nell'inizializzare i dati in un database creato da Code First o sviluppato da Migrations. Questi dati sono spesso dati di test, ma possono anche essere dati di riferimento come elenchi di studenti noti, corsi, ecc. Quando i dati vengono inizializzati, esegue le seguenti operazioni:
- Verifica se il database di destinazione esiste già o meno.
- In caso affermativo, il modello Code First corrente viene confrontato con il modello archiviato nei metadati nel database.
- Il database viene eliminato se il modello corrente non corrisponde al modello nel database.
- Il database viene creato se è stato eliminato o non esisteva in primo luogo.
- Se il database è stato creato, viene chiamato il metodo Seed dell'inizializzatore.
Il metodo Seed accetta l'oggetto contesto del database come parametro di input e il codice nel metodo utilizza quell'oggetto per aggiungere nuove entità al database. Per eseguire il seed dei dati nel database, è necessario sovrascrivere il metodo Seed. Diamo un'occhiata al seguente esempio in cui alcuni dei dati predefiniti vengono avviati nel database in una classe interna.
private class UniDBInitializer<T> : DropCreateDatabaseAlways<MyContext> {
protected override void Seed(MyContext context) {
IList<Student> students = new List<Student>();
students.Add(new Student() {
FirstMidName = "Andrew",
LastName = "Peters",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
students.Add(new Student() {
FirstMidName = "Brice",
LastName = "Lambson",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
students.Add(new Student() {
FirstMidName = "Rowan",
LastName = "Miller",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
foreach (Student student in students)
context.Students.Add(student);
base.Seed(context);
}
}
Nel codice precedente, la tabella degli studenti viene inizializzata. È necessario impostare questa classe di inizializzazione DB nella classe di contesto come illustrato nel codice seguente.
public MyContext() : base("name=MyContextDB") {
Database.SetInitializer<MyContext>(new UniDBInitializer<MyContext>());
}
Di seguito è riportata l'implementazione della classe completa della classe MyContext, che contiene anche la classe inizializzatore DB.
public class MyContext : DbContext {
public MyContext() : base("name=MyContextDB") {
Database.SetInitializer<MyContext>(new UniDBInitializer<MyContext>());
}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
private class UniDBInitializer<T> : DropCreateDatabaseAlways<MyContext> {
protected override void Seed(MyContext context) {
IList<Student> students = new List<Student>();
students.Add(new Student() {
FirstMidName = "Andrew",
LastName = "Peters",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
students.Add(new Student() {
FirstMidName = "Brice",
LastName = "Lambson",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
students.Add(new Student() {
FirstMidName = "Rowan",
LastName = "Miller",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
});
foreach (Student student in students)
context.Students.Add(student);
base.Seed(context);
}
}
}
Quando l'esempio precedente viene compilato ed eseguito, è possibile visualizzare i dati in un database come mostrato nell'immagine seguente.
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
Entity Framework 4.3 include una nuova funzionalità Code First Migrations che consente di evolvere in modo incrementale lo schema del database man mano che il modello cambia nel tempo. Per la maggior parte degli sviluppatori, questo è un grande miglioramento rispetto alle opzioni di inizializzazione del database dalle versioni 4.1 e 4.2 che richiedevano di aggiornare manualmente il database o rilasciarlo e ricrearlo quando il modello cambiava.
Prima di Entity Framework 4.3, se si dispone già di dati (diversi dai dati seed) o di stored procedure esistenti, trigger, ecc. Nel database, queste strategie utilizzavano per eliminare l'intero database e ricrearlo, in modo da perdere i dati e altro DB oggetti.
Con la migrazione, aggiornerà automaticamente lo schema del database, quando il modello cambia senza perdere dati esistenti o altri oggetti del database.
Utilizza un nuovo inizializzatore di database chiamato MigrateDatabaseToLatestVersion.
Esistono due tipi di migrazione:
- Migrazione automatizzata
- Migrazione basata sul codice
Migrazione automatizzata
La migrazione automatizzata è stata introdotta per la prima volta in Entity Framework 4.3. Nella migrazione automatica non è necessario elaborare manualmente la migrazione del database nel file di codice. Ad esempio, per ogni modifica dovrai anche modificare le classi del tuo dominio. Ma con la migrazione automatizzata devi solo eseguire un comando nella console di Gestione pacchetti per farlo.
Diamo un'occhiata al seguente processo passo passo di migrazione automatizzata.
Quando utilizzi l'approccio Code First, non hai un database per la tua applicazione.
In questo esempio inizieremo con le nostre 3 classi base come Studente, Corso e Iscrizione come mostrato nel codice seguente.
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
[Index]
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Di seguito è riportata la classe di contesto.
public class MyContext : DbContext {
public MyContext() : base("MyContextDB") {}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Prima di eseguire l'applicazione, è necessario abilitare la migrazione automatica.
Step 1 - Aprire la console di Package Manager da Strumenti → NuGet Package Manager → Console di Package Manager.
Step 2 - Per abilitare la migrazione automatizzata, eseguire il seguente comando nella console di Gestione pacchetti.
PM> enable-migrations -EnableAutomaticMigrations:$true
Step 3 - Una volta eseguito correttamente il comando, crea una classe di configurazione sigillata interna nella cartella Migration del progetto, come mostrato nel codice seguente.
namespace EFCodeFirstDemo.Migrations {
using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration<EFCodeFirstDemo.MyContext> {
public Configuration() {
AutomaticMigrationsEnabled = true;
ContextKey = "EFCodeFirstDemo.MyContext";
}
protected override void Seed(EFCodeFirstDemo.MyContext context) {
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
// context.People.AddOrUpdate(
// p ⇒ p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
}
}
}
Step 4 - Impostare l'inizializzatore del database nella classe di contesto con la nuova strategia di inizializzazione del database MigrateDatabaseToLatestVersion.
public class MyContext : DbContext {
public MyContext() : base("MyContextDB") {
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext,
EFCodeFirstDemo.Migrations.Configuration>("MyContextDB"));
}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Step 5- Hai impostato la migrazione automatica. Quando esegui la tua applicazione, si occuperà automaticamente della migrazione, quando modifichi il modello.
Step 6- Come puoi vedere, una tabella di sistema __MigrationHistory viene creata anche nel tuo database con altre tabelle. In __MigrationHistory, la migrazione automatizzata conserva la cronologia delle modifiche al database.
Step 7- Quando aggiungi un'altra classe di entità come classe di dominio ed esegui la tua applicazione, verrà creata la tabella nel tuo database. Aggiungiamo la seguente classe StudentLogIn.
public class StudentLogIn {
[Key, ForeignKey("Student")]
public int ID { get; set; }
public string EmailID { get; set; }
public string Password { get; set; }
public virtual Student Student { get; set; }
}
Step 8 - Non dimenticare di aggiungere il DBSet per la classe sopra menzionata nella tua classe di contesto come mostrato nel codice seguente.
public virtual DbSet<StudentLogIn> StudentsLogIn { get; set; }
Step 9 - Esegui di nuovo l'applicazione e vedrai che la tabella StudentsLogIn viene aggiunta al tuo database.
I passaggi sopra menzionati per le migrazioni automatiche funzioneranno solo per la tua entità. Ad esempio, per aggiungere un'altra classe di entità o rimuovere la classe di entità esistente, la migrazione verrà eseguita correttamente. Ma se aggiungi o rimuovi qualsiasi proprietà alla tua classe di entità, verrà generata un'eccezione.
Step 10 - Per gestire la migrazione delle proprietà è necessario impostare AutomaticMigrationDataLossAllowed = true nel costruttore della classe di configurazione.
public Configuration() {
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
ContextKey = "EFCodeFirstDemo.MyContext";
}
Migrazione basata sul codice
Quando si sviluppa una nuova applicazione, il modello di dati cambia frequentemente e ogni volta che il modello cambia, non è più sincronizzato con il database. È stato configurato Entity Framework per eliminare e ricreare automaticamente il database ogni volta che si modifica il modello di dati. La migrazione basata sul codice è utile quando si desidera un maggiore controllo sulla migrazione.
Quando aggiungi, rimuovi o modifichi le classi di entità o modifichi la classe DbContext, la prossima volta che esegui l'applicazione, il database esistente verrà eliminato automaticamente, ne crea uno nuovo che corrisponde al modello e lo semi con i dati di test.
La funzionalità Migrazioni Code First risolve questo problema consentendo a Code First di aggiornare lo schema del database invece di eliminare e ricreare il database. Per distribuire l'applicazione, dovrai abilitare le migrazioni.
Ecco la regola di base per migrare le modifiche nel database:
- Abilita migrazioni
- Aggiungi migrazione
- Aggiornare il database
Diamo un'occhiata al seguente processo passo passo della migrazione della base di codice.
Quando si utilizza l'approccio code first, non si dispone di un database per la propria applicazione.
In questo esempio inizieremo di nuovo con le nostre 3 classi di base come Studente, Corso e Iscrizione come mostrato nel codice seguente.
public class Enrollment {
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public Grade? Grade { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Course {
public int CourseID { get; set; }
public string Title { get; set; }
[Index]
public int Credits { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Di seguito è riportata la classe di contesto.
public class MyContext : DbContext {
public MyContext() : base("MyContextDB") {
Database.SetInitializer(new MigrateDatabaseToLatestVersion<
MyContext, EFCodeFirstDemo.Migrations.Configuration>("MyContextDB"));
}
public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Enrollment> Enrollments { get; set; }
public virtual DbSet<Student> Students { get; set; }
}
Step 1 - Prima di eseguire l'applicazione è necessario abilitare la migrazione.
Step 2 - Aprire la console di Gestione pacchetti da Strumenti → Gestione pacchetti NuGet → Console Gestione pacchetti.
Step 3 - La migrazione è già abilitata, ora aggiungi la migrazione nella tua applicazione eseguendo il seguente comando.
PM> add-migration "UniDB Schema"
Step 4 - Quando il comando è stato eseguito con successo, vedrai un nuovo file creato nella cartella Migration con il nome del parametro passato al comando con un prefisso timestamp come mostrato nell'immagine seguente.
Step 5 - È possibile creare o aggiornare il database utilizzando il comando "update-database".
PM> Update-Database -Verbose
Il flag "-Verbose" specifica di mostrare le istruzioni SQL applicate al database di destinazione nella console.
Step 6 - Aggiungiamo un'altra proprietà "Età" nella classe dello studente e quindi eseguiamo l'istruzione di aggiornamento.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public int Age { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
Quando esegui PM → Update-Database –Verbose, quando il comando viene eseguito con successo vedrai che la nuova colonna Age è stata aggiunta nel tuo database.
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
In questo capitolo impareremo come migrare le modifiche nel database quando ci sono più classi DbContext nell'applicazione.
- Più DbContext sono stati introdotti per la prima volta in Entity Framework 6.0.
- Più classi di contesto possono appartenere a un singolo database o due database diversi.
Nel nostro esempio, definiremo due classi Context per lo stesso database. Nel codice seguente sono presenti due classi DbContext per Student e Teacher.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
public class MyStudentContext : DbContext {
public MyStudentContext() : base("UniContextDB") {}
public virtual DbSet<Student> Students { get; set; }
}
public class Teacher {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime HireDate { get; set; }
}
public class MyTeacherContext : DbContext {
public MyTeacherContext() : base("UniContextDB") {}
public virtual DbSet<Teacher> Teachers { get; set; }
}
Come puoi vedere nel codice sopra, ci sono due modelli chiamati "Studente" e "Insegnante". Ognuno è associato a una particolare classe di contesto corrispondente, ovvero Student è associato a MyStudentContext e Teacher è associato a MyTeacherContext.
Ecco la regola di base per migrare le modifiche nel database, quando sono presenti più classi Context all'interno dello stesso progetto.
enable-migrations -ContextTypeName <DbContext-Name-with-Namespaces> MigrationsDirectory: <Migrations-Directory-Name>
Add-Migration -configuration <DbContext-Migrations-Configuration-Class-withNamespaces> <Migrations-Name>
Update-Database -configuration <DbContext-Migrations-Configuration-Class-withNamespaces> -Verbose
Abilitare la migrazione per MyStudentContext eseguendo il comando seguente nella console di Gestione pacchetti.
PM→ enable-migrations -ContextTypeName:EFCodeFirstDemo.MyStudentContext
Una volta eseguito, aggiungeremo il modello nella cronologia della migrazione e per questo, dobbiamo attivare il comando add-migration nella stessa console.
PM→ add-migration -configuration EFCodeFirstDemo.Migrations.Configuration Initial
Aggiungiamo ora alcuni dati nelle tabelle Studenti e Insegnanti nel database.
static void Main(string[] args) {
using (var context = new MyStudentContext()) {
//// Create and save a new Students
Console.WriteLine("Adding new students");
var student = new Student {
FirstMidName = "Alain",
LastName = "Bomer",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
//Age = 24
};
context.Students.Add(student);
var student1 = new Student {
FirstMidName = "Mark",
LastName = "Upston",
EnrollmentDate = DateTime.Parse(DateTime.Today.ToString())
//Age = 30
};
context.Students.Add(student1);
context.SaveChanges();
// Display all Students from the database
var students = (from s in context.Students orderby s.FirstMidName
select s).ToList<Student>();
Console.WriteLine("Retrieve all Students from the database:");
foreach (var stdnt in students) {
string name = stdnt.FirstMidName + " " + stdnt.LastName;
Console.WriteLine("ID: {0}, Name: {1}", stdnt.ID, name);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
using (var context = new MyTeacherContext()) {
//// Create and save a new Teachers
Console.WriteLine("Adding new teachers");
var student = new Teacher {
FirstMidName = "Alain",
LastName = "Bomer",
HireDate = DateTime.Parse(DateTime.Today.ToString())
//Age = 24
};
context.Teachers.Add(student);
var student1 = new Teacher {
FirstMidName = "Mark",
LastName = "Upston",
HireDate = DateTime.Parse(DateTime.Today.ToString())
//Age = 30
};
context.Teachers.Add(student1);
context.SaveChanges();
// Display all Teachers from the database
var teachers = (from t in context.Teachers orderby t.FirstMidName
select t).ToList<Teacher>();
Console.WriteLine("Retrieve all teachers from the database:");
foreach (var teacher in teachers) {
string name = teacher.FirstMidName + " " + teacher.LastName;
Console.WriteLine("ID: {0}, Name: {1}", teacher.ID, name);
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
Quando viene eseguito il codice sopra, vedrai che vengono create due tabelle diverse per due modelli diversi, come mostrato nell'immagine seguente.
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.
Prima di Entity Framework 6, Entity Framework non riconosceva entità o tipi complessi annidati all'interno di altre entità o tipi complessi. Quando Entity Framework ha generato il modello, i tipi nidificati sono semplicemente scomparsi.
Diamo un'occhiata a un semplice esempio in cui abbiamo il nostro modello di base con tre entità Studente, Corso e Iscrizione.
Aggiungiamo una proprietà Identity, che è un tipo Person. Person è un'altra entità, contiene le proprietà BirthDate e FatherName.
In termini di Entity Framework, poiché non ha identità ed è parte di un'entità, è un tipo complesso di Entity Framework e in realtà abbiamo avuto supporto per i tipi complessi sin dalla prima versione di Entity Framework.
Il tipo Person non è annidato come mostrato nel codice seguente.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public Person Identity { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
public class Person {
public Person(string fatherName, DateTime birthDate) {
FatherName = fatherName;
BirthDate = birthDate;
}
public string FatherName { get; set; }
public DateTime BirthDate { get; set; }
}
Entity Framework saprà come rendere persistenti i tipi Person quando viene usato anche nelle versioni precedenti.
Utilizzando Entity Framework Power Tool vedremo come Entity Framework interpreta il modello. Fare clic con il tasto destro sul file Program.cs e selezionare Entity Framework → Visualizza Entity Data Model (sola lettura)
Ora vedrai che la proprietà Identity è definita nella classe Student.
Se questa classe Person non verrà usata da nessun'altra entità, è possibile nidificarla all'interno della classe Student, ma questa versione precedente di Entity Framework non riconosce i tipi annidati.
Nella versione precedente, si genera nuovamente il modello, non solo il tipo non è riconosciuto, ma poiché non è presente, non è presente nemmeno la proprietà, quindi Entity Framework non persisterà affatto il tipo Person.
public class Student {
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public Person Identity { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
public class Person {
public Person(string fatherName, DateTime birthDate) {
FatherName = fatherName;
BirthDate = birthDate;
}
public string FatherName { get; set; }
public DateTime BirthDate { get; set; }
}
}
Con Entity Framework 6, le entità annidate e i tipi complessi vengono riconosciuti. Nel codice sopra, puoi vedere che Person è annidato nella classe Student.
Quando usi Entity Framework Power Tool per mostrare come Entity Framework interpreta il modello questa volta, c'è la vera proprietà Identity e il tipo complesso Persona. Quindi Entity Framework manterrà quei dati.
Ora puoi vedere che Identity è un tipo di entità annidato, che non era supportato prima di Entity Framework 6.
Si consiglia di eseguire l'esempio precedente in modo graduale per una migliore comprensione.