Introduzione a Entity Framework 4.0

Scritto da  Alessandro Mostarda il mercoledì 25 agosto 2010
Linguaggio: C#,VB   •  Framework: 4.0   •  Livello: 100

Download sorgenti


La nuova release di Visual studio 2010 ha portato con se una nuova versione di Entity Framework (4.0). Rispetto al predecessore sono stati introdotte nuove funzionalità ed apportati notevoli miglioramenti, il che rende questo ORM uno strumento di sviluppo veramente valido per la gestione della persistenza dei dati sul DB.

In questo articolo non si parlerà del confronto con la versione precedente, ma verrà illustrato il funzionamento partendo dall'inizio.

Prima di addentrarci nello specifico, vorrei introdurre il concetto di ORM. Un'ORM è un sistema software che consente di interagire con un database mediante l'utilizzo di classi e di LINQ e non di comandi standard SQL. Questo ci consente di slegarci dalla struttura fisica e logica del database, e ci consente di avere un approccio Object-Oriented.

Dopo questa brevissima introduzione andiamo a vedere come iniziare ad utilizzare EF4. Per prima cosa dobbiamo aggiungere un nuovo elemento al nostro progetto, come si evince dalla figura sottostante:

Figura 1

Dopo aver selezionato la voce evidenziata, inizia il wizard di creazione . E' necessario soffermarsi sulla prima richiesta del wizard. Infatti ci viene chiesto se generare le classi da un database esistente, oppure se generare prima le classi e, poi, farci generare lo script di creazione del DB. Entrambe le modalità presentano vantaggi e svantaggi. Infatti nel primo caso(Database-First), possiamo decidere di creare un nuovo modellandolo secondo le caratteristiche del db stesso oppure di sviluppare l'applicazione nel caso che il database sia già stato disegnato. L'altra modalità invece ci consente di concentrarci sulla creazione delle nostre classi di dominio, rimandando la creazione del database ad un secondo momento. Quando si termina di generare tutte le classi di dominio, possiamo generare lo script per il database tramite il designer incluso in Visual Studio. E' chiaro che tale script è ignaro delle specifiche features che ci mette a disposizione il motore del database su cui verranno generate le tabelle. Tanto per fare un esempio lo script non prevede la generazione di indici sulle tabelle.

E' comunque importante sapere che ci sono entrambe le modalità a nostra disposizione ed abbiamo, quindi, la possibilità di effettuare la scelta caso per caso.

Figura 2

Dopo aver eseguito tutti i passi necessari del wizard, Visual Studio mostra l'editor di Entity Framework. All'interno di questo editor troviamo l'insieme delle nostre tabelle che abbiamo importato in precedenza.

Cosa che inizialmente può sembrare strana è che non vediamo le stored procedure eventualmente importate, ma questo accade solo perché, per utilizzarle, dobbiamo fare un'operazione sull'editor.

Figura 3

Infatti dobbiamo cliccare con il testo destro ed effettuare l'operazione evidenziata nell'immagine sopra. A questo punto l'editor ci mostra questa finestra che ci serve per impostare tutti i parametri necessari per l'utilizzo di stored procedure.

Figura 4

 

Selezioniamo la nostra stored procedure, clicchiamo sul tasto "Get Column Information", e poi clicchiamo sul "Create New Complex Type" per farci generare una nuova classe che conterrà il risultato della nostra Stored Procedure, Se invece la nostra stored procedure contiene esattamente gli stessi campi di una classe generata in precedenza, allora possiamo decidere di mapparla verso una Entity già esistente.

Al termine di queste operazioni di configurazione sul nostro editor, Entity Framework genera per noi tutte le classi necessarie per il colloquio con il db. Infatti, da adesso, abbiamo a disposizione nel nostro progetto una Classe Context (EF4Entitites nel progetto di esempio), che si occupa di aprire la connessione con il database e di effettuare le query e/o gli aggiornamenti. Questa classe contiene tante proprietà quante sono le nostre entity ed inoltre contiene tanti metodi quante sono le nostre stored procedure.  Inoltre, vengono, anche, generate le classi che mappano le tabelle e/o stored procedure, come si può notare dal codice sorgente allegato.

 

Il linguaggio designato per interrogare il nostro DB è Linq to Entites, pertanto effettueremo le nostre query, quasi allo stesso modo con cui effettuamo query sulle normali collection utilizzando LINQ. In realtà non tutti i comandi di LINQ sono a disposizione, in quanto alcuni di essi non sono trasformabili in codice SQL, pertanto diciamo che LINQ to Entities contiene un set leggermente ridotto di istruzioni rispetto a LINQ to Objects.

SELECT

Ma vediamo ora alcuni esempi pratici per capirne il funzionamento. Se vogliamo recuperare l'elenco completo dei nostri clienti la sintassi è la seguente:

using (ef4Entities ctx = new ef4Entities())
{
    Griglia.DataSource = ctx.Clienti;
}

 

Già a primo impatto possiamo notare l'esigua quantità di codice necessaria per avere a disposizione il risultato. Questo è uno dei pregi fondamentali di Entity Framework, poiché la produttività aumenta in maniera decisa dovendo scrivere pochissime righe di codice.

WHERE

Se invece volessimo avere l'elenco di tutti i clienti della città di Roma, scriveremmo la nostra query in questo modo:

using (ef4Entities ctx = new ef4Entities())
{
    Griglia.DataSource = ctx.Clienti.Where(w => w.Citta == "Roma");

ORDER BY

La prossima query ci restituisce l'elenco dei clienti ordinate per nome:

using (ef4Entities ctx = new ef4Entities())
{
    Griglia.DataSource = ctx.Clienti.OrderBy(o => o.Nome);
}

ANONYMOUS TYPE

La query successiva, invece,  ci restituisce il nome, il cognome ed il numero totale delle fatture emesse a ciascun cliente:

using (ef4Entities ctx = new ef4Entities())
{
           Griglia.DataSource =(from x in ctx.Clienti
                         select new
                            {Cognome = x.Cognome,
                             Nome = x.Nome,
                             NumeroFatture = x.Fatture.Count
           }).OrderByDescending (o => o.NumeroFatture);
}
 

 

INSERT

Nell'esempio successivo verrà mostrato come inserire un nuovo record.  Notare che, per salvare i dati nel db, viene invocato il metodo AddObject della proprietà Clienti dell'ObjectContext e successivamente il metodo SaveChanges.

using (var ctx = new ef4Entities())
{
Clienti cliente= new Clienti();
cliente.Cognome = txtCognome.Text;
cliente.Nome = txtNome.Text;
cliente.Indirizzo = txtIndirizzo.Text;
cliente.Cap = txtCap.Text;
cliente.Citta = txtCitta.Text;
cliente.Provincia = txtProvincia.Text;
cliente.PartitaIva = txtPartitaIva.Text;
cliente.CodiceFiscale = txtCodiceFiscale.Text;

ctx.Clienti.AddObject(cliente);
ctx.SaveChanges();
}



 

UPDATE

Per quanto riguarda la modifica invece EF4 necessita di 2 passaggi. Il primo serve per recuperare l'oggetto cliente esistente sul db, poi vengono modificati i valori ed infine chiamato il metodo SaveChanges.

using (var ctx = new ef4Entities())
{
    Clienti cliente = ctx.Clienti.Where(w => w.Id == Id).First();
    cliente.Cognome = txtCognome.Text;
    cliente.Nome = txtNome.Text;
    cliente.Indirizzo = txtIndirizzo.Text;
    cliente.Cap = txtCap.Text;
    cliente.Citta = txtCitta.Text;
    cliente.Provincia = txtProvincia.Text;
    cliente.PartitaIva = txtPartitaIva.Text;
    cliente.CodiceFiscale = txtCodiceFiscale.Text;

    ctx.SaveChanges();
}

 

DELETE

La cancellazione invece avviene con questo codice:

using (ef4Entities ctx = new ef4Entities())
{
int IdCliente = (int)Griglia.SelectedRows[0].Cells[0].Value;
var cliente = ctx.Clienti.First(f => f.Id == IdCliente);

ctx.Clienti.DeleteObject(cliente);
ctx.SaveChanges();
}

 

STORED PROCEDURE

Il totale del fatturato dei nostri clienti può essere fatto tramite una stored procedure che può essere invocata in questo modo:

using (ef4Entities ctx = new ef4Entities())
{
Griglia.DataSource = ctx.FatturatoClienti();
}

 

LEFT JOIN + SUM

Se invece volessimo fare la stessa operazione tramite Linq to entities il codice diventa più complesso:

using (var ctx = new ef4Entities())
   {
        Griglia.DataSource = (from clienti in ctx.Clienti

join fatture in ctx.Fatture on clienti.Id equals fatture.IdCliente into gruppo1
from lista1 in gruppo1.DefaultIfEmpty()
join righefattura in ctx.RigheFattura on lista1.Id equals righefattura.IdFattura into gruppo2
from lista2 in gruppo2.DefaultIfEmpty()
group lista2 by new { clienti.Cognome, clienti.Nome } into Finale
select new
{

Cognome = Finale.Key.Cognome,
Nome = Finale.Key.Nome,
Fatturato = Finale.Sum(s => s.PrezzoTotale + s.Imposta) == null ? 0 : Finale.Sum(s => s.PrezzoTotale + s.Imposta)
}).OrderByDescending(o => o.Fatturato);
}

 

In questo caso abbiamo implementato 2 left join tra le 3 tabelle ed abbiamo estratto il risultato. La cosa più importante da notare è l'istruzione "DefaultIfEmpty" che consente di trasformare un operazione di INNER JOIN in un'operazione di LEFT JOIN.

 

MULTIPLE INSERT

L'ultima cosa da far vedere è l'aggiunta di una fattura, che deve registrare sia i dati di testata, che quelli di dettaglio. L'operazione può essere effettuate in questo modo.

using (var ctx = new ef4Entities())
{
Fatture fatt = new Fatture();

//Muovo i valori
fatt.IdCliente = (int)cmbClienti.SelectedValue;
fatt.DataEmissione = Convert.ToDateTime((txtData.Text));
fatt.Numero = txtNumero.Text;

//Creo le righe di dettaglio;
foreach (DataGridViewRow riga in Griglia.Rows)
{
var rigaFattura = new RigheFattura();
rigaFattura.DescrizioneArticolo = (string)riga.Cells[1].Value;
rigaFattura.PrezzoUnitario = (decimal)riga.Cells[2].Value;
rigaFattura.PrezzoTotale = (decimal)riga.Cells[3].Value;
rigaFattura.Imposta = (decimal)riga.Cells[4].Value;


fatt.RigheFattura.Add(rigaFattura);
}

//Creo l'oggetto fattura
ctx.Fatture.AddObject(fatt);
ctx.SaveChanges();

this.Hide();
}



 

La cosa che può risultare interessante è che, prima prepariamo la nostra classe con la compilazione delle nostre righe di dettaglio, e poi la aggiungiamo alla proprietà Fatture del nostro ObjectContext. Infatti non è necessario invocare il metodo AddObject per ciascuna riga di dettaglio della Fattura. Questo avviene perché Entity framework aggiunge automaticamente tutti gli elementi della collection righeFattura della classe Fattura.

 

L'ultima cosa di cui volevo parlare in questo articolo introduttivo era del file EDMX. All'inizi di questo articolo si è parlato del designer. In realtà tutte le informazioni relative al designer vengono memorizzate in un file (EDMX), che altro non è che un semplice file XML composto di 4 parti:

  • SSDL: Questo sezione del file EDMX contiene la strutture delle tabelle e delle stored procedure del Database
  • CSDL: questa sezione contiene la struttura delle nostre classi, che come vedremo nei prossimi può tranquillamente non coincidere con le tabelle
  • C-S: questa sezione contiene tutte le informazioni di mappatura tra le tabelle del database e le nostre classi
  • Designer: questa sezione contiene informazioni riguardanti solo ed esclusivamente l'editor di Entity framework, e non ha alcun impatto sul funzionamento di EF all'interno del nostro applicativo.

Questa piccola appendice risulta utile in quanto in alcuni casi potrà essere comodo e/o necessario modificare manualmente questo file, piuttosto che utilizzare l'editor.

Conclusioni

Questo articolo risulta essere solo una breve introduzione a Entity framework, per il quale servirebbe un libro per spiegarne tutte le funzionalità. Nei prossimi articoli parleremo, invece, delle features più avanzate. Ma già da adesso si può apprezzare l'intuitività di questo tool, che ha lo scopo di facilitare l'accesso ai dati e la mappatura dei dati del DB verso le nostre classi.

Appendice

Per ragioni di spazio il codice è riportato solo in C#, ma, nell'esempio allegato, è presente anche il sorgente nel linguaggio VB.NET. Per far funzionare l'esempio allegato è necessario creare un database su Sql server e ripristinare il backup (EF4.bak) contenuto nello .zip allegato.

 


Tags: orm,Entity Framework

 
x