Introduzione a Entity Framework 4.1
a cura di Antonio Catucci (requisiti: conoscenza di Entity Framework)Premessa
Entity Framework 4 ha rappresentato un punto importante per quanto riguarda le tecnologie di accesso ai dati, perché ha migliorato la versione precedente (la 1.0) e ha introdotto una serie di funzionalità molto interessanti, come, a esempio, il pattern Model-First che permette di creare un database ex-novo a partire dal modello dati. Questo approccio, insieme al classico Database-First, ha come requisito quello di avere sempre un file di modello EDMX per il mapping delle entità con le tabelle del database.Recentemente, il team di Entity Framework ha rilasciato un'estensione chiamata Entity Framework 4.1 (a oggi in RC) per introdurre un terzo approccio di sviluppo, diverso da quelli esistenti, chiamato Code-First (noto anche come CodeOnly). Come il nome lascia intendere, si tratta di una metodologia che consente di mappare un modello dati con un database esclusivamente via codice, evitando l'uso di un file di mapping. Sebbene questo approccio possa sembrare "scomodo", in realtà vedremo che non lo è, sia per il modo in cui questo mapping si applica, che per i vantaggi che esso introduce.
L'installazione di Entity Framework 4.1 (che potete scaricare da qui ) non modifica l'attuale versione 4.0 inclusa in .Net Framework 4, ma aggiunge una nuova libreria chiamata EntityFramework.dll che va referenziata nei progetti.
Come funziona
Il concetto alla base di CodeFirst è quello di separare completamente le entità dalla logica di mapping cercando di renderle il più possibile agnostiche. Sebbene questo sia già possibile in EF 4 grazie al supporto per le classi POCO, è comunque necessario avere il file di mapping EDMX, mentre con CodeFirst quest'ultimo non è più necessario. Un'altra importante novità di Entity Framework 4.1 è l'introduzione di un nuovo set di API che facilitano ulteriormente la gestione delle entità, grazie al fatto di essere separate dal set attualmente presente in EF 4, pur sfruttandone tutte le potenzialità.Visto che il file EDMX non è più necessario, come mappiamo un database con le nostre entità? Le modalità sono due:
- Naming convention
- Attributi + codice
La prima modalità è basata su una serie di regole da applicare alla nomenclatura delle entità, in modo tale che queste siano mappate automaticamente al database dal motore di EF. Banalmente: il nome delle classi e delle proprietà deve coincidere con il nome delle tabelle e dei campi. Quindi una tabella Customer sarà mappata con la classe Customer e i campi Id e CompanyName con le omonime proprietà.
Vediamo un esempio per chiarire meglio il concetto.
Un primo esempio
Vediamo come mappare la tabella Employee del database Northwind. Il primo passo è creare l'omonima classe utilizzando gli stessi nomi presenti nel database:Public Class Employee Property EmployeeId As Integer Property LastName As String Property FirstName As String Property BirthDate As DateTime End ClassSuccessivamente occorre creare la classe context:
Public Class NorthwindContext Inherits DbContext Property Employees As DbSet(Of Employee) End ClassLa prima differenza con EF 4 è la classe di contesto NorthwindContext, che gestisce tutte le operazioni tra entità e database, che non eredita più da ObjectContext ma da DbContext; inoltre ciascuna entità viene identificata con il tipo DbSet(Of T) , analogo a ObjectSet(Of T) .
Prima di eseguire il nostro esempio, configuriamo la stringa di connessione nel file .config:
<add name="NorthwindContext" connectionString="data source=(localhost);initial catalog=Northwind;uid=<userid>;pwd=<password>;" providerName="System.Data.SqlClient"/>Ora proviamo ad eseguire una semplice query di selezione per verificare che tutto funzioni:
Public Sub SelectEmployees() Using ctx As New NorthwindContext Dim query = From e In ctx.Employees Order By e.LastName Select e query.ToList.ForEach(Sub(item) Console.WriteLine("[{0}] {1}", item.EmployeeId, item.LastName)) End Using End SubSe tutto è stato configurato correttamente, il risultato sarà il seguente:
![]()
Tutto funziona senza avere creato il file di mapping .EDMX, sfruttando il meccanismo della naming convention delle classi.
Nell'esempio, la classe Employee è la copia della corrispondente tabella Employees del database Northwind:
![]()
Dunque, EF non fa altro che mappare le entità basandosi sui nomi delle classi e delle proprietà, che devono coincidere. Per quanto riguarda il nome delle tabelle, si segue la convenzione della Pluralization, ovvero si esprime l'entità al singolare e il corrispondente DbSet al plurale.
Quella della naming convention viene applicata (facoltativamente) anche alla stringa di connessione, il cui nome nel file di configurazione deve coincidere con il nome della classe di contesto NorthwindContext evitando, così, di specificarla ogni volta nel codice. Naturalmente è sempre possibile il contrario aggiungendo un costruttore opportuno nella classe NorthwindContext:Sub New(ByVal connectionString As String) MyBase.New(connectionString) End SubRelazioni tra tabelle
Naturalmente è possibile mappare le relazioni tra le tabelle seguendo sempre la stessa logica. Mappiamo la tabella Orders che ha una relazione con la tabella Employees:Public Class Order Property OrderId As Integer Property EmployeeId As Integer Overridable Property Employee As Employee Property OrderDate As DateTime End ClassCome già avviene in EF 4 si crea sia il campo chiave (EmployeeId) che il riferimento all'entità (Employee) rimanendo invariato tutto il resto (compreso il Lazy Loading):
Using ctx As New NorthwindContext Dim item = ctx.Orders.Find(10250) Console.WriteLine("[{0}] {1}", item.OrderId, item.Employee.LastName) End UsingManipolazione dei dati
I metodi per la gestione delle entità introducono alcuni cambiamenti e aggiungono nuove funzionalità. A esempio è stato introdotto il metodo Find(), che permette di recuperare un record specificando il valore chiave senza dover costruire la query LINQ:Public Sub FindEmployee() Using ctx As New NorthwindContext Dim item = ctx.Employees.Find(6) Console.WriteLine("[{0}] {1}", item.EmployeeID, item.LastName) End Using End SubIl metodo accetta un numero variabile di valori (ParamArray), qualora l'entità avesse una chiave basata su più campi.
Altra novità è l'aggiunta dell'Extension Include() tipizzata, che consente, finalmente, di includere entità utilizzando una lambda expression, anziché il nome in forma di stringa. A esempio, ipotizzando di visualizzare l'impiegato a cui si riferisce un ordine, si può scrivere:
Using ctx As New NorthwindContext Dim item = ctx.Orders.Include(Function(o) o.Employee). FirstOrDefault( Function(ord) ord.OrderId = 10250) End UsingTutte le altre operazioni come la creazione di nuovi record, eliminazione e Attach al contesto restano invariati.
Creazione del database
Una novità molto interessante è la possibilità di creare al volo il database basato sul modello, qualora questo non esista. A esempio, se modifichiamo il nome del database nella stringa di connessione in NorthwindTemp ed eseguiamo una qualunque query, automaticamente EF creerà il database basato sulla definizione corrente delle entità che, nel nostro caso, prevede solo le tabelle Orders ed Employees:
![]()
Con tanto di relazione e definizione di chiavi! (NOTA: l'utente del database deve avere i permessi di dbcreator).
Ma come ha fatto EF a capire quali sono le Primary Key delle entità? La risposta è ancora: la nomenclatura. Infatti, tutte le property chiamate Id, <NomeClasse>Id oppure Key, <NomeClasse>Key sono candidate automaticamente a diventare chiavi primarie. Inoltre, se il tipo è numerico (int, short o long), questa è impostata automaticamente come Identity.
Data Annotation
Fino ad ora abbiamo visto come mappare un database sfruttando esclusivamente il metodo della naming convention, ma ovviamente questo non è l'unico meccanismo disponibile, perché non sempre (anzi, direi: raramente) un database esistente segue queste regole di nomenclatura. Per questo motivo è possibile sfruttare la Data Annotation, basata sull'uso di attributi e codice. Gli attributi disponibili si trovano nel namespace System.ComponentModel.DataAnnotation.Riprendendo gli esempi precedenti, se volessimo usare le classi in lingua italiana anziché inglese, dovremmo scrivere:
<Table("Employees")> Public Class Impiegato <Column("EmployeeId"), Key()> Property Codice As Integer <Column("LastName")> Property Cognome As String <Column("FirstName")> Property Nome As String Property BirthDate As DateTime End ClassVengono, cioè, utilizzati una serie di attributi per specificare semplicemente i valori di mapping con il database. In particolare, si usano <Table> e <Column> per indicare rispettivamente il nome della tabella e dei relativi campi, mentre l'attributo <Key> indica la chiave primaria.
Come detto precedentemente, questo approccio si presta molto bene in quegli scenari in cui il database è già esistente e non rispetta la nomenclatura richiesta da EF, ma ha anche lo svantaggio di 'sporcare' le entità, sebbene limitatamente agli attributi.
Per evitare l'uso degli attributi, si può utilizzare il codice, facendo l'override del metodo OnModelCreating() della classe base DbContext:
Protected Overrides Sub OnModelCreating(ByVal modelBuilder As System.Data.Entity.DbModelBuilder) modelBuilder.Entity(Of Impiegato).ToTable("Employees") MyBase.OnModelCreating(modelBuilder) End SubQuesto metodo diventa fondamentale, in alcuni casi, perché gli attributi non supportano tutte le casistiche, infatti non è possibile, a esempio, specificare una chiave come Identity se non via codice:
modelBuilder.Entity(Of Impiegato).HasKey(Function(k) k.Codice) _ .Property(Function(p) p.Codice) _ .HasColumnName("EmployeeId") _ .HasDatabaseGeneratedOption( _ DatabaseGeneratedOption.Identity)Questo codice imposta la proprietà Codice come chiave ( HasKey), la definisce come Identity (HasDatabaseGeneratedOption) e la mappa con il campo Employee (HasColumnName).
Conclusioni
Da questa breve panoramica si può arguire che l'approccio CodeFirst rappresenta sicuramente un'alternativa molto utile nello sviluppo di un modello dati senza dover obbligatoriamente definire un file di mapping, che è necessario, invece, nelle altre modalità. Inoltre, grazie all'introduzione di un set di nuove API, la gestione dei dati e del database offre sicuramente potenzialità aggiuntive semplificando le operazioni già oggi disponibili con Entity Framework 4.