Entity Framework 4 :POCO, Complex Type e Stored Procedure
Scritto da
Pietro Libro
il
mercoledì 12 gennaio 2011
Linguaggio:
•
Framework:
•
Livello: 100
Download sorgenti
Riprendiamo in questo articolo le novità diEntity Framework 4,
approfondendo alcuni concetti avanzati che ci permettono di
essere più produttivi durante il nostro lavoro di sviluppatori. Per
chi si fosse perso la prima parte dell'articolo, può trovarlo al
link seguente: Introduzione a Entity
Framework 4.0
Sicuramente, una delle novità più evidenti della nuova versione di
Entity Framework 4.0 (EF 4 d'ora in avanti) , è il supporto allo
sviluppo Model First: approccio che permette di partire
dal modello dati ed in un secondo momento generare il database
fisico (nel caso di EF 4, generabile tramite i tool integrati in
Visual Studio 2010).
Un'altra (grande) feature riguarda il supporto verso i tipi
POCO (plain-old CLR objects): ovvero la capacità di
utilizzare oggetti che fanno parte del business per eseguire la
persistenza dei dati, senza dover "decorare" con attributi o
ereditare (o implementare) altre classi come invece accadeva nella
precedente versione di Entity Framework. Per l'implementazioni di
relazioni tra oggetti (uno-a-molti o molti-a-molti) è sufficiente
utilizzare collezioni che implementano l'interfaccia
ICollection<>.
Alcuni dei vantaggi "immediati" che si possono avere sfruttando
queste funzionalità sono: migrazione da vecchie procedure,
sviluppo "Code-First", TDD (Test-Driven Development), Testing.
Cercheremo di chiarire alcuni di questi aspetti tramite gli
esempi proposti nell'articolo: utilizzeremo un database di SQL
Server 2008 già pronto (gli script per la generazione del database
si trovano insieme al codice allegato), ragion per cui non
utilizzeremo un approccio Model First. Partiamo con
una soluzione vuota di Visual Studio 2010, che chiameremo
DoumsEF4, a cui aggiungiamo due C# Library Project:
- DomusEF4Data, contente il modello dei dati.
- DomusEF4POCO, contenente le classi POCO.
Nel Solution Explorer, selezioniamo il progetto DomusEF4Data, e
aggiungiamo a quest'ultimo un riferimento al progetto DomusEF4Poco.
La nostra soluzione dovrebbe essere simile alla seguente:

Eliminiamo i file "Class1.cs" presenti nei due progetti.
Nel progetto DomusEF4Data aggiungiamo un nuovo item
ADO.NET Entity Data Model che identifichiamo come
DomusEF4DataModel.edmx. Nel Wizard che ci viene mostrato,
utilizziamo la funzione Generate from database per utilizzare il
database creato tramite gli script allegati:



Dopo aver premuto Next, è visualizzata una seconda
schermata dove possiamo selezionare la connessione al database (se
non presente è necessario crearne una nuova), poi ancora click su
Next :a questo punto il Wizard ci chiede di scegliere
quali oggetti del nostro database vogliamo aggiungere al modello
dati, selezioniamo tutte le tabelle tranne
sysdiagrams, e la Stored Procedure
ElencoPersoneAssociazioni (che utilizzeremo nell'ultima
parte dell'articolo) ed impostiamo il namespace del nostro modello
a DomusEF4Data. Click su Finish, dopo qualche
istante, nell'elenco dei file del nostro progetto vedremo comparire
il file DomusEF4DataModel.edmx. Prima di continuare,
visualizziamo la finestra delle proprietà di quest'ultimo file e
cancelliamo il contenuto della proprietà Custom Tool
(perché saremo noi a scrivere il codice tipicamente generato da
Visual Studio 2010). Così facendo non verrà generata nessuna classe
Entity con cui siamo normalmente abituati ad interagire, ma avremo
solo il file .edmx che descrive il modello concettuale, il modello
di archiviazione ed il mapping tra questi due modelli. A questo
punto, il Designer di Visual Studio dovrebbe apparire in questo
modo:

Il diagramma della figura precedente differisce dalla struttura
fisica delle tabelle su database, dove invece abbiamo una terza
tabella per la gestione della relazione molti-a-molti:

Torniamo al codice, selezioniamo il progetto
DomusEF4Poco e aggiungiamo due nuove classi (le nostre
POCO), Persona e Associazione, il cui codice è
rispettivamente:
using System;
using System.Collections.Generic;
namespace DomusEF4Poco
{
public class Persona
{
public Int32 ID { get; set; }
public string Nome { get; set; }
public string Cognome { get; set; }
public DateTime DataNascita { get; set; }
public string Via { get; set; }
public string Cap { get; set; }
public string Comune { get; set; }
public string Provincia { get; set; }
public IList<Associazione> Associazioni { get; set; }
}
public class Associazione
{
public int Id { get; set; }
public string Denominazione { get; set; }
public string Sigla { get; set; }
public IList<Persona> Persone { get; set; }
}
}
Da notare come le Navigation Properties
(Associazione.Persone e Persona.Associazioni) presenti nel modello
dei dati, siano state tradotte in IList<T>. Per
poter eseguire query e le operazioni di persistenza dei dati,
aggiungiamo nel progetto DomusEF4Data la classe
DomusContext che deriva da
System.Data.Objects.ObjectContext (è necessario aggiungere
al progetto un riferimento alla libreria
System.Data.Entity.dll):
using System.Data.Entity;
using System.Data.Objects;
namespace DomusEF4Data
{
public class DomusContext : System.Data.Objects.ObjectContext
{
private ObjectSet<DomusEF4Poco.Persona> _persone = null;
private ObjectSet<DomusEF4Poco.Associazione> _associazioni = null;
public DomusContext()
: base("name=DomusDb", "DomusDb")
{
_persone = CreateObjectSet<DomusEF4Poco.Persona>();
_associazioni = CreateObjectSet<DomusEF4Poco.Associazione>();
}
public ObjectSet<DomusEF4Poco.Persona> Persone
{
get { return _persone; }
}
public ObjectSet<DomusEF4Poco.Associazione> Associazioni
{
get { return _associazioni; }
}
}
}
CreateObjectSet crea una nuova istanza della classe
ObjectSet<>, classe 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 trovati su MSDN: http://msdn.microsoft.com/en-us/library/dd382944.aspx
e http://msdn.microsoft.com/en-us/library/dd412719.aspx
.
Per eseguire dei test, aggiungiamo una classe "manager" che si
occupi di eseguire l'inserimento di oggetti di tipo Persona ed
ottenere l'elenco delle "persone" già persistite,
utilizzando la classe DomusContext:
using System.Data;
using System.Linq;
using System.Collections.Generic;
public class PersonaManager
{
public int AggiungiPersona(DomusEF4Poco.Persona p)
{
using (DomusEF4Data.DomusContext db = new DomusEF4Data.DomusContext())
{
db.AddObject("Persone", p);
int result = db.SaveChanges();
}
return p.Id;
}
public IList<DomusEF4Poco.Persona> ElencoPersone()
{
using (DomusEF4Data.DomusContext db = new DomusEF4Data.DomusContext())
{
var q = from c in db.Persone orderby c.Cognome select c;
return q.ToList();
}
}
}
Aggiungiamo alla soluzione un nuovo progetto C# Console
Application (che impostiamo come progetto di avvio) e modifichiamo
il Main con del codice simile al seguente (nota bene: per
far funzionare il tutto è necessario impostare i riferimenti
ai progetti già presenti nella soluzione, alla libreria
System.Data.Entity.dll e aver aggiunto un file
App.Config con la stringa di connessione al database):
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
namespace DomusTestApplication
{
class Program
{
static void Main(string[] args)
{
DomusEF4Data.PersonaManager manager = new DomusEF4Data.PersonaManager();
DomusEF4Poco.Persona p = new DomusEF4Poco.Persona();
p.Cognome = "LIBRO";
p.Nome = "PIETRO";
p.Cap = "00100";
p.Comune = "Roma";
p.Via = "Via Casilina Vecchia X";
p.Provincia = "RM";
p.DataNascita = DateTime.Parse("1954-02-01");
Console.WriteLine("La persona {0} {1} e' stata aggiunta con ID {2}",
p.Nome, p.Cognome, manager.AggiungiPersona(p));
Console.WriteLine("Nel DB sono presenti le seguenti persone:");
foreach (DomusEF4Poco.Persona per in manager.ElencoPersone())
{
Console.WriteLine("{0} {1} ID={2}", per.Nome, per.Cognome, per.ID);
}
Console.ReadLine();
}
}
}
Proviamo ad eseguire l'applicazione, dovremmo ottenere qualcosa
simile al contenuto dello screenshot seguente:

Proviamo ora a gestire la relazione tra persone ed associazioni:
aggiungiamo un overload del metodo
PersonaManager.Aggiungi(…) che accetti come
parametri d'ingresso un'istanza della classe Persona ed
una di Associazione, in questo modo:
using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
namespace DomusTestApplication
{
class Program
{
static void Main(string[] args)
{
DomusEF4Data.PersonaManager manager = new DomusEF4Data.PersonaManager();
DomusEF4Poco.Persona p = new DomusEF4Poco.Persona();
p.Cognome = "LIBRO";
p.Nome = "PIETRO";
p.Cap = "00100";
p.Comune = "Roma";
p.Via = "Via Casilina Vecchia X";
p.Provincia = "RM";
p.DataNascita = DateTime.Parse("1954-02-01");
Console.WriteLine("La persona {0} {1} e' stata aggiunta con ID {2}",
p.Nome, p.Cognome, manager.AggiungiPersona(p));
Console.WriteLine("Nel DB sono presenti le seguenti persone:");
foreach (DomusEF4Poco.Persona per in manager.ElencoPersone())
{
Console.WriteLine("{0} {1} ID={2}", per.Nome, per.Cognome, per.ID);
}
Console.ReadLine();
}
}
}
Modifichiamo PersonaManager.ElencoPersone() in modo da
caricare automaticamente anche le associazioni a cui ogni persona
appartiene.
Modifichiamo il Main in questo modo:
class Program
{
static void Main(string[] args)
{
DomusEF4Data.PersonaManager manager = new DomusEF4Data.PersonaManager();
DomusEF4Poco.Persona p = new DomusEF4Poco.Persona();
p.Cognome = "LIBRO";
p.Nome = "PIETRO";
p.Cap = "00100";
p.Comune = "Roma";
p.Via = "Via Casilina Vecchia X";
p.Provincia = "RM";
p.DataNascita = DateTime.Parse("1954-02-01");
Console.WriteLine("La persona {0} {1} e' stata aggiunta con ID {2}",
p.Nome, p.Cognome, manager.AggiungiPersona(p));
Console.WriteLine("Nel DB sono presenti le seguenti persone:");
foreach (DomusEF4Poco.Persona per in manager.ElencoPersone())
{
Console.WriteLine("{0} {1} ID={2}", per.Nome, per.Cognome, per.ID);
}
Console.ReadLine();
}
}
Eseguendo l'applicazione dovremmo ottenere qualcosa del
genere:

La magia si compie grazie ai metadati memorizzati nel file
.edmx, che permettono al .NET Framework di eseguire l'accesso al
database e di eseguire la persistenza dei dati. Proviamo ora
ad eseguire qualche piccola modifica al Data Model precedente per
introdurre il concetto dei Complex Type.
Nel nostro modello dati, le proprietà candidate alla creazione di
un tipo complesso sono: Via, Cap, Comune e Provincia presenti
nell'Entity Persona. Una volta selezionate, possiamo eseguirne il
refactor scegliendo dal menu contestuale la voce Refactor
into New Complex Type:

L'entità Persona ora presenta una nuova proprietà
ComplexType1 che rinominiamo opportunamente in Indirizzo,
stessa cosa per la voce ComplexType1 presente nel
Model Browser:

A questo punto, l'EDM Designer dovrebbe essere simile alla
seguente figura:

Visualizzando il Mapping Details, possiamo osservare
come Visual Studio mappa le entità alle tabelle del database
in presenza di tipi complessi (tipo_complesso.proprietà):

Per avere un'applicazione completa, non ci resta che modificare
le classi POCO: selezioniamo il progetto DomusEF4Poco ed
aggiungiamo una nuova classe Indirizzo:
public class Indirizzo
{
public string Via { get; set; }
public string Comune { get; set; }
public string Cap { get; set; }
public string Provincia { get; set; }
}
modifichiamo il codice della classe Persona:
public class Persona
{
public Int32 ID { get; set; }
public string Nome { get; set; }
public string Cognome { get; set; }
public DateTime DataNascita { get; set; }
public Indirizzo Indirizzo { get; set; }
public IList<Associazione> Associazioni { get; set; }
}
Modifichiamo il Main:
DomusEF4Data.PersonaManager manager = new DomusEF4Data.PersonaManager();
DomusEF4Poco.Persona persona = new DomusEF4Poco.Persona();
persona.Associazioni = new List<DomusEF4Poco.Associazione>();
persona.Cognome = "Tizio";
persona.Nome = "Caio";
persona.DataNascita = DateTime.Parse("1967-12-14");
persona.Indirizzo = new Indirizzo()
{
Comune = "ROMA",
Cap = "00100",
Provincia = "RM",
Via = "Via Una Via Di Roma"
};
DomusEF4Poco.Associazione associazione = new DomusEF4Poco.Associazione();
associazione.Persone = new List<DomusEF4Poco.Persona>();
associazione.Denominazione = "SOCIETA' di ROMA s.r.l.";
associazione.Indirizzo = "Via Indirizzo Società.";
manager.Aggiungi(persona, associazione);
Console.WriteLine("Nel DB sono presenti le seguenti persone:");
foreach (DomusEF4Poco.Persona p in manager.ElencoPersone())
{
Console.WriteLine("{0} {1} ID={2}", p.Nome, p.Cognome, p.ID);
Console.WriteLine("Via: {0}", p.Indirizzo.Via);
Console.WriteLine("Comune: {0}", p.Indirizzo.Comune);
Console.WriteLine("Provincia: {0}", p.Indirizzo.Provincia);
Console.WriteLine("Appartiene alle seguenti associazioni");
foreach (DomusEF4Poco.Associazione ass in p.Associazioni)
{
Console.WriteLine("===>{0}", ass.Denominazione);
}
}
Ed eseguiamo:

I tipi complessi possono essere utilizzati per mappare i
risultati provenienti da una Stored Procedure. Nella
precedente versione di Entity Framework i risultati delle
Stored Procedure potevano (anzi dovevano) essere mappate
solo verso Entity, che a loro volta dovevano far
riferimento a tabelle o viste effettivamente esistenti (spesso si
rischiava di aggiungere al modello dati molte tabelle e/o viste
fittizie). Con i Complex Type questo non è più necessario.
Vediamo un esempio: supponiamo di avere la seguente procedura
(presente sul db allegato):
CREATE PROCEDURE ElencoPersoneAssociazioniAS
SELECT
dbo.Persone.Cognome + ' ' + dbo.Persone.Nome AS Nominativo, dbo.Associazioni.Denominazione
FROM dbo.Persone INNER JOIN
dbo.PersoneAssociazioni ON dbo.Persone.ID = dbo.PersoneAssociazioni.ID_Persona INNER JOIN
dbo.Associazioni ON dbo.PersoneAssociazioni.ID_Associazione = dbo.Associazioni.ID
E' una semplice Select che mette in Join (tramite la tabella di
associazione) le tabelle Persone ed Associazioni e restituisce
l'elenco delle persone e la denominazione dell'associazione di
appartenenza. Il primo passo è eseguire l'importazione della Stored
Procedure tramite la voce di menu contestuale visualizzabile
dall'Entity Designer:

Nella finestra visualizzata scegliamo la Stored Procedure
ElencoPersoneAssociazioni a cui assegniamo il nome
GetPersoneAssociazioni. Premiamo il tasto Get Column
Information per recuperare il nome delle colonne restituite
dalla procedura e premiamo il tasto Create New Complext
Type per creare un nuovo tipo complesso che ritroveremo nel
Model Browse:

Aggiungiamo alla nostra classe DomusContext il codice
seguente (è necessario aggiungere Using System.Linq) :
public IList<PersonaAssociazione> GetPersoneAssociazioni()
{
return this.ExecuteFunction<PersonaAssociazione>("GetPersoneAssociazioni",
new ObjectParameter[] { }).ToList();
}
Quindi, aggiungiamo una nuova classe PersonaAssociazione al
progetto contenente le classi POCO:
public class PersonaAssociazione
{
public string Nominativo { get; set; }
public string Denominazione { get; set; }
}
Non ci resta che modificare il codice del Main:
DomusEF4Data.PersonaManager manager = new DomusEF4Data.PersonaManager();
foreach (PersonaAssociazione perAss in manager.ElencoPersoneAssociazioni())
{
Console.WriteLine(String.Format("Nominativo: {0}, Associazione: {1}",
perAss.Nominativo, perAss.Denominazione));
}
A questo punto proviamo ad eseguire l'applicazione, ottenendo
qualcosa di simile:

Siamo giunti così alla conclusione di questo secondo
appuntamento dedicato a Entity Framework 4, versione più matura e
sicuramente più "vicina" ad altri prodotti come (N)Hibernate, se
paragoniamo il tutto con la versione precedente di EF. Nei
prossimi articoli, oltre a parlare delle nuove funzionalità
introdotte con la versione CTP 5 di EF, vedremo l'interazione di EF
con WCF e la gestione della concorrenza tipico delle
soluzioni Enterprise.
Il codice riportato nell'articolo è in C#, ma la soluzione
allegata all'articolo contiene i progetti sia in C# che VB.NET.
Tags: POCO,Complex Type,Entity Framework 4