Entity Framework 4 :POCO, Complex Type e Stored Procedure

Scritto da  Pietro Libro il mercoledì 12 gennaio 2011
Linguaggio: C#,VB   •  Framework: 4.0   •  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:

  1. DomusEF4Data, contente il modello dei dati.
  2. 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:

Figura_1

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:

Figura_2

Figura_3

Figura_4

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:

Figura_5

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:

Figura_6

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:

Figura_7

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:

Figura_8

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:

Figura_9

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

Figura_10

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

Figura_11

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

Figura_12

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:

Figura_13

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:

Figura_15

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:

Figura_15

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:

Figura_16

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

 
x