Nel post precedente abbiamo visto una delle novità più evidenti della nuova versione di Entity Framework, il supporto allo sviluppo Model First. Un'altra feature riguarda il supporto verso i tipi POCO (plain-old CLR objects): in pratica, abbiamo la possibilità di utilizzare le nostre classi custom, costituenti il Domain Model dell’applicazione, con il Data Model, senza dover far nulla (o quasi), è infatti sufficiente mappare le nostre classi con l’entità del modello dei dati per avere a disposizione gli stessi (o quasi) comportamenti (query, inserimento ed aggiornamento dei dati etc…) che si hanno con le classi entità con cui siamo abituati a lavorare ora. Alcuni dei vantaggi che mi vengono in mente, sfruttando questa nuova funzione, sono: possibilità di migrare vecchie procedure affinché possano utilizzare i vantaggi offerti da EF, sviluppo “Code-First”, sviluppo TDD (Test-Driven Development), Testing.
In questo esempio, andremo ad utilizzare il database creato nel post precedente, ma con una nuova soluzione di Visual Studio 2010 beta 2, denominata EF4Features, a cui andremo ad aggiungere due C# Library Project:
- EF4FeaturesData, contente il modello dei dati.
- EF4FeaturesPOCO, contenente i le nostre classi POCO.
Prima di proseguire, nel progetto EF4FeaturesData, andiamo ad aggiungere un riferimento al progetto EF4FeaturesPOCO. A questo punto la nostra soluzione dovrebbe essere simile a qualcosa di questo tipo:
Nel progetto EF4FeaturesData aggiungiamo un nuovo item ADO.NET Entity Data Model, che possiamo chiamare EF4FeaturesDataModel.edmx. Però, a differenza del post precedente, invece di partire da un modello dati vuoto, nel Wizard, scegliamo Generate from database:
Dopo aver premuto Next, verrà visualizzata una seconda schermata dove potremo selezionare la connessione al database (che dovrebbe essere già presente nell’elenco delle connessioni disponibili). Premendo nuovamente su next, il Wizard ci chiede di scegliere quali oggetti del nostro database vogliamo aggiungere al nostro modello: scegliamo tutte le tabelle tranne sysdiagrams ed impostiamo il namespace del nostro modello come EF4FeaturesData:
A questo punto premiamo su Finish e dopo qualche secondo, nell’elenco dei file del nostro progetto vedremo comparire il file EF4FeaturesDataModel.edmx. Prima di continuare, visualizziamo la finestra delle proprietà del file EF4FeaturesDataModel.edmx e cancelliamo il contenuto della proprietà Custom Tool (perché saremo noi a scrivere il codice che generalmente è automaticamente generato da Visual Studio).
Selezioniamo il progetto EF4FeaturesPOCO e aggiungiamo due nuove classi (le nostre POCO), Ente e Persona, il cui codice è rispettivamente:
public class Ente
{
public int Id { get; set; }
public string Denominazione { get; set; }
public string Sigla { get; set; }
public List<Persona> Persone { get; set; }
}
e
public class Persona
{
public int Id { get; set; }
public string Nome { get; set; }
public string Cognome { get; set; }
public string Indirizzo { get; set; }
public List<Ente> Enti {get;set;}
}
Da notare come le Navigation Properties (Ente.Persone e Persone.Enti) presenti nel nostro modello dei dati, siano state tradotte in List<T>. Per poter eseguire query e la persistenza dei dati, abbiamo necessità di creare la nostra classe EF4FeaturesContext derivata da ObjectContext. Veramente banale:
public class EF4FeaturesContext : ObjectContext
{
private ObjectSet<EF4FeaturesPOCO.Persona>_persone = null;
private ObjectSet<EF4FeaturesPOCO.Ente> _enti = null;
public EF4FeaturesContext()
: base("name=EF4DataModel", "EF4DataModel")
{
_persone = CreateObjectSet<EF4FeaturesPOCO.Persona>();
_enti = CreateObjectSet<EF4FeaturesPOCO.Ente>();
}
public ObjectSet<EF4FeaturesPOCO.Persona> Persone
{
get { return _persone; }
}
public ObjectSet<EF4FeaturesPOCO.Ente> Enti
{
get { return _enti; }
}
}
Nel codice, CreateObjectSet crea una nuova istanza della classe ObjectSet<>, utilizzata per eseguire query, aggiungere, modificare ed eliminare oggetti dello specifico tipo. ObjectSet<> a sua volta estende le funzionalità della classe ObjectQuery<> presente nella versione precedente di Entity Framework. Ulteriori dettagli su queste due classi possono essere recuperati dalla versione beta di MSDN: http://msdn.microsoft.com/en-us/library/dd412652(VS.100).aspx (ObjectContext.CreateObjectSet(TEntity)) e http://msdn.microsoft.com/en-us/library/dd412719(VS.100).aspx (ObjectSet(TEntity)).
A questo punto, possiamo creare una classe “manager” che si occupi di eseguire l’inserimento di una nuova persona ed ottenere l’elenco delle persone già registrate utilizzando EF4FeaturesContext:
public class PersonaManager
{
public int Aggiungi(EF4FeaturesPOCO.Persona p)
{
using (EF4FeaturesData.EF4FeaturesContext context = new EF4FeaturesData.EF4FeaturesContext())
{
context.AddObject("Persone", p);
int result = context.SaveChanges();
}
return p.Id;
}
public List<EF4FeaturesPOCO.Persona> OttieniElencoPersone()
{
using (EF4FeaturesData.EF4FeaturesContext context = new EF4FeaturesData.EF4FeaturesContext())
{
var q = from c in context.Persone orderby c.Cognome select c;
return q.ToList();
}
}
}
Testiamo il tutto aggiungendo alla nostra soluzione un nuovo progetto C# Console Application, scrivendo nel Main qualcosa di questo tipo (dopo aver impostato i riferimenti ai progetti già presenti nella soluzione):
EF4FeaturesData.PersonaManager manager = new EF4FeaturesData.PersonaManager();
Persona p = new Persona();
p.Cognome = "LIBRO";
p.Nome = "PIETRO";
p.Indirizzo = "Via di qualcosa";
Console.WriteLine("La persona {0} {1} e' stata aggiunta con ID {2}",
p.Nome,p.Cognome, manager.Aggiungi(p));
Console.WriteLine("Nel DB sono presenti le seguenti persone:");
foreach (Persona per in manager.OttieniElencoPersone())
{
Console.WriteLine("{0} {1} ID={2}", per.Nome, per.Cognome, per.Id);
}
Eseguendo l’applicazione, otteniamo:
Proviamo ora a gestire la relazione: aggiungiamo un overload del metodo PersonaManager.Aggiungi(…) che accetti un’istanza della classe persona ed una di Ente, in questo modo:
public int Aggiungi(EF4FeaturesPOCO.Persona p,EF4FeaturesPOCO.Ente e)
{
using (EF4FeaturesData.EF4FeaturesContext context = new EF4FeaturesData.EF4FeaturesContext())
{
context.AddObject("Persone", p);
context.AddObject("Enti", e);
p.Enti.Add(e);
e.Persone.Add(p);
int result = context.SaveChanges();
}
return p.Id;
}
Modifichiamo PersonaManager.OttieniElencoPersone() così (in modo da caricare automaticamente anche gli enti di ogni persona):
public List<EF4FeaturesPOCO.Persona> OttieniElencoPersone()
{
using (EF4FeaturesData.EF4FeaturesContext context = new EF4FeaturesData.EF4FeaturesContext())
{
var q = from c in context.Persone.Include ("Enti") orderby c.Cognome select c;
return q.ToList();
}
}
Ora il Main:
EF4FeaturesData.PersonaManager manager = new EF4FeaturesData.PersonaManager();
Persona p = new Persona();
p.Enti = new List<Ente>();
p.Cognome = "PINCO";
p.Nome = "PALLO";
p.Indirizzo = "Via di qualcosa";
Ente e = new Ente();
e.Persone = new List<Persona>();
e.Denominazione = "NUOVA SOCIETA' s.r.l.";
e.Sigla = "NS";
manager.Aggiungi(p, e);
Console.WriteLine("Nel DB sono presenti le seguenti persone:");
foreach (Persona per in manager.OttieniElencoPersone())
{
Console.WriteLine("{0} {1} ID={2}", per.Nome, per.Cognome, per.Id);
Console.WriteLine("Appartiene ai seguenti enti:");
foreach (Ente ent in per.Enti)
{
Console.WriteLine("===>{0}", ent.Denominazione);
}
}
Eseguendo nuovamente l’applicazione otterremo qualcosa di questo tipo:
La magia si compie grazie ai metadati memorizzati nel file .edmx, che permettono al .NET Framework di eseguire l’accesso al database e persistere i dati. Lo sforzo che il team di ADO.NET sta portando avanti è veramente notevole. Qualche osservazione: primo, per grandi progetti sarebbe molto oneroso scrivere “a manina” le varie classi POCO, Entity Framework 4.0 ci viene incontro mediante l’utilizzo di un tool molto potente basato su T4, attraverso script personalizzabili, secondo, in questo post non sono stati affrontati i problemi relativi a Lazy Loading, Change Tracking e Proxies.