Condividi tramite


Introduzione ad Entity Framework con C#, parte I (it-IT)


Introduzione

In questo articolo, primo di una breve serie, vedremo come poter utilizzare Entity Framework nelle nostre applicazioni, sviluppate mediante i vari linguaggi che Visual Studio ci consente di utilizzare. Negli esempi riportati in questo articolo, e probabilmente nei successivi, verrà fatto uso di C# in ambito WinForms, ma - come detto - questa scelta non andrà ad inficiare una diversa destinazione d'utilizzo che lo sviluppatore potrà ritenere opportuna.

Cos'è Entity Framework

Entity Framework (da qui in poi, EF) è il framework ORM (object-relational mapping) che Microsoft ci mette a disposizione nell'ambito dello sviluppo .NET (dalla versione 3.5 SP1 in poi). La sua finalità è quella di astrarre i legami ad un database relazionale, facendo in modo che lo sviluppatore possa rapportarsi alle entità di database come ad un insieme di oggetti, e quindi a classi e loro proprietà. Vengono rese trasparenti le fasi di connessione al database, come anche le istruzioni che la nostra applicazione dovrà inviare in linguaggio nativo al database stesso. In sostanza, parliamo di un disaccoppiamento tra le nostre applicazioni e la logica di accesso ai dati: questo si rivela un plus importante, per esempio, se dovessimo aver necessità di passare - nell'ambito di uno stesso programma - a database di produttori differenti, in quanto non sarebbe richiesto di rivedere il modo e le istruzioni con cui ci interfacciamo al gestore dati di turno.

Approcci di utilizzo di Entity Framework

Allo stato attuale, EF permette principalmente due tipi di approccio legati al suo impiego. Essi sono Database-First e Code-First (il primo dei quali assente a partire da EF7, ma ancora valido fino alla versione 6.1.3). La differenza tra i due approcci è evidente fin dal loro nome: con Database-First, ci troviamo nella condizione in cui dobbiamo modellare un database pre-esistente (e quindi, partire da esso per derivare i nostri oggetti), mentre nella modalità Code-First, saranno le nostre classi - che dovremo redigere assegnando loro le proprietà rappresentanti i campi di tabella - a determinare la struttura della base dati. Un punto importante da notare su quest'ultima osservazione, è che Code-First non ci obbliga necessariamente a lavorare inizialmente in assenza di database: potremo infatti modellare le classi di un database esistente, e connetterci ad esso per effettuare le consuete operazioni di I/O. Possiamo affermare che i due approcci, al di là di alcune particolarità strumentali che vedremo, rappresentino una sorta di indice di priorità rispetto a chi comanda nel determinare la struttura dei dati con cui l'applicazione avrà a che fare: "prima il database" (da cui derivano le classi) o "prima il codice" (da cui può essere strutturato un modello di base dati)

Referenziare Entity Framework nel progetto

Prima di vedere un paio di esempi in merito all'utilizzo di EF, è necessario referenziarlo all'interno del nostro progetto, ossia di renderne le librerie accessibili alla soluzione. Creiamo quindi un nuovo progetto in Visual Studio, scegliendo il template di cui abbiamo necessità (nell'esempio, come anticipato, selezionerò C# su WinForms). Salviamo quindi la soluzione (importante per evitare avvisi in fase di aggiunta di EF), e quindi apriamo il gestore di pacchetti Nuget.

In esso, cercheremo il package Entity Framework, e semplicemente lo aggiungeremo alla nostra solution mediante pressione del tasto «Installa». Al termine dell'operazione, vedremo come tra le References di progetto saranno state inserite quelle relative ad EF.

Siamo quindi pronti per utilizzare le potenzialità del nostro ORM relativamente allo sviluppo. Lasciando per appuntamenti successivi le diverse particolarità relative ad un impiego specifico, quindi scendendo un po' più nel dettaglio tecnico, vediamo in questa sede come poter eseguire una prima connessione a database e modellazione delle classi, seguendo i due paradigmi precedentemente citati.

Database-First

Vediamo anzitutto la modalità Database-First, che - come abbiamo detto - è principalmente indirizzata a database già esistenti, da cui ricavare un modello. Ricordiamo ancora una volta che, perlomeno durante la stesura del presente articolo, da EF7 in poi tale modalità è stata soppressa in favore di Code-First, ed è un dettaglio importante da tenere a mente quando si andrà a valutare l'analisi di una soluzione. 

Preparare la base dati

Ai fini esemplificativi, è stato creato un database di nome TECHNET, al cui interno sono presenti due tabelle: "Articoli" e "Famiglie". Lo script T-SQL che crea le entità, così come le vedremo nel proseguo, è il seguente:

USE [TECHNET]
GO
 
SET ANSI_NULLS ON
GO
 
SET QUOTED_IDENTIFIER ON
GO
 
CREATE TABLE  [dbo].[Articoli](
    [IdArticolo] [int] IDENTITY(1,1) NOT  NULL,
    [CodArt] [nvarchar](25) NOT NULL,
    [DesArt] [nvarchar](50) NOT NULL,
    [CodFamiglia] [nvarchar](6) NOT NULL,
 CONSTRAINT [PK_Articoli] PRIMARY KEY CLUSTERED 
(
    [IdArticolo] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON  [PRIMARY]
) ON  [PRIMARY]
 
GO
 
ALTER TABLE  [dbo].[Articoli] ADD   CONSTRAINT [DF_Articoli_CodArt]  DEFAULT ('') FOR  [CodArt]
GO
 
ALTER TABLE  [dbo].[Articoli] ADD   CONSTRAINT [DF_Articoli_DesArt]  DEFAULT ('') FOR  [DesArt]
GO
 
ALTER TABLE  [dbo].[Articoli] ADD   CONSTRAINT [DF_Articoli_CodFamiglia]  DEFAULT ('') FOR  [CodFamiglia]
GO
 
CREATE TABLE  [dbo].[Famiglie](
    [CodFamiglia] [nvarchar](6) NOT NULL,
    [DesFamiglia] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_Famiglie] PRIMARY KEY CLUSTERED 
(
    [CodFamiglia] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON  [PRIMARY]
) ON  [PRIMARY]
 
GO

Una volta create le tabelle, popoliamole con alcuni dati. 

USE [TECHNET]
 
INSERT INTO  Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART001', 'ARTICOLO TEST'  , 'FAM01')
INSERT INTO  Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART002', 'PRODOTTO PROVA', 'FAM01')
INSERT INTO  Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART003', 'ART. 003',  'FAM02')
INSERT INTO  Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART004', 'ART. 004',  'FAM02')
INSERT INTO  Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART005', 'ART. 005',  'FAM02')
INSERT INTO  Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART006', 'ART. 006',  'FAM02')
INSERT INTO  Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART007', 'ART. 007',  'FAM03')
INSERT INTO  Articoli(CodArt, DesArt, CodFamiglia) VALUES ('ART008', 'ART. 008',  'FAM04')
 
INSERT INTO  Famiglie(CodFamiglia, DesFamiglia) VALUES ('FAM01', 'PROD. MECCANICI')
INSERT INTO  Famiglie(CodFamiglia, DesFamiglia) VALUES ('FAM02', 'PROD. ELETTRONICI')
INSERT INTO  Famiglie(CodFamiglia, DesFamiglia) VALUES ('FAM03', 'RICAMBI')
INSERT INTO  Famiglie(CodFamiglia, DesFamiglia) VALUES ('FAM04', 'IMPORT')

Si noterà il banale legame, non esplicitamente dichiarato, tra le due tabelle: CodFamiglia è il campo che lega la tabella Articoli con la tabella Famiglie, per accede alla descrizione di quest'ultima. Eseguito lo script, avremo a disposizione qualche record per verificare, ad esempio, l'esito di una lettura dati.

Modellare la classi con EF

Vediamo ora come poter modellare, lato Visual Studio, le classi a cui ci riferiremo nel nostro programma per utilizzare queste entità dati.

Aggiungiamo un nuovo elemento alla solution, andando a richiamare, dal menù Visual C# Items » Data, un oggetto di tipo ADO.NET Entity Data Model, che chiameremo «TechnetModello».

Una volta confermata la scelta, Visual Studio lancerà il Wizard dedicato agli Entity Data Model, in modo da permetterci di indicare quale tipo di modello utilizzare. La nostra scelta, in questo caso, sarà «EF Designer from database», che realizza il paradigma Database-First.

Proseguendo, ci verrà chiesto di stabilire una connessione al nostro database. Premiamo quindi «New Connection» ed andiamo ad impostare i parametri di connessione. Nell'immagine seguente, un esempio da adattare alle circostante di sviluppo reali.

Confermando ancora, ci verrà mostrata la stringa di connessione generata, e potremo salvarla in App.config, assegnandole un nome, per l'utilizzo futuro.

Infine, ci verrà richiesto quali entità derivare nel modello. Nel nostro caso, selezioniamo le due tabelle create, lasciando il resto delle impostazioni come proposto.

Il Wizard completerà le operazioni del caso, andando a generare un modello dati composto dalle classi rappresentanti le entità di database, e mostrandone una rappresentazione grafica, sulla quale potremo agire per creare ad esempio associazioni esplicite tra tabelle, o variare alcune delle proprietà indicate.

*Importante:* seguendo il paradigma Database-First, abbiamo già accennato a come sia il database a rappresentare il contesto che modella le classi, e non viceversa. Pertanto, se in questa modalità andremo a modificare qualche proprietà, e ci troveremo poi nella situazione di dover rigenerare il modello, tali variazioni - presenti solo lato codice - andranno perdute, sovrascritte da quanto indicato sul database.

Curiosando nelle classi che il Wizard ha creato per noi, ne notiamo due di particolare interesse, ovvero quelle che - gerarchicamente - risultano figlie del file TechnetModello.tt, ovvero: Articoli.cs e Famiglie.cs.

Aprendole, noteremo che ricalcano la struttura delle tabelle cui si riferiscono, esponendo come nome di classe quello della tabella stessa, ed i campi della loro controparte, opportunamente convertiti ai tipi utilizzabili lato codice.

Una semplice interrogazione

Accenniamo velocemente al modo in cui, in questo contesto, possiamo effettuare una lettura dei dati, per rimandare discussioni più approfondite ad articoli futuri.

Supponiamo di voler estrarre, dalla tabella Articoli, il record avente CodArt = 'ART001', e di volerne emetterne a video la descrizione (campo DesArt). Dal momento che il nostro contesto dati è stato trasformato in classi, potremo utilizzare la comoda sintassi LINQ, applicata alle entità figlie del contesto dati TECHNETEntities.

L'interrogazione appena accennata si risolve quindi banalmente come segue:

using (TECHNETEntities db = new TECHNETEntities())
{
    Articoli art = db.Articoli.Where((x) => x.CodArt == "ART001").FirstOrDefault();
 
    MessageBox.Show(art.DesArt);
}

Ovvero, viene istanziato un oggetto db, di tipo TECHNETEntities, tra le cui proprietà troviamo le tabelle referenziate da EF, sulle quali poter eseguire - ad esempio - le comuni operazioni di selezione. Importante notare come, oltre alle entità di per sé, disponiamo ora di tipi legati alle tabelle: la variabile art è stata dichiarata come di tipo Articoli, ovvero possiede tutte le proprietà della sua classe.

Eseguiamo quindi il metodo Where sulla classe Articoli, mediante funzione lambda che selezioni il codice articolo desiderato, restituendo il primo oggetto trovato mediante metodo FirstOrDefault (non gestiamo qui controlli di validità del dato, che ovviamente in un contesto reale devono essere previste).

A questo punto, utilizzando la variabile art, possiamo riferirci alle sue proprietà, e chiedendo quindi l'emissione di una MessageBox avente testo pari alla proprietà DesArt di art, otterremo il seguente output:

Già a questo punto, risulta evidente l'enorme potenziale di Entity Framework, che per lo sviluppatore si traduce in fattori di primaria importanza, come il considerevole risparmio di tempo, ed il poter dedicare i propri sforzi alla soluzione in sé, piuttosto che al layer intermedio tra essa e la base dati, ora completamente gestito. 

Relazioni tra tabelle e ricadute sul modello

Nel modellare la base dati, abbiamo stabilito che le due entità Articoli e Famiglie fossero tra loro collegate. In realtà, non abbiamo definito Foreign Keys che possano aver istruito EF su come legare le due tabelle che - di fatto - risultano quindi indipendenti. In questa condizione, dovremo ricorrere ad istruzioni LINQ più elaborate, che realizzino i join che mancano a livello di modello.

Supponiamo di voler selezionare l'articolo ART005, e desideriamo esporne a video sia la descrizione prodotto che quella della famiglia di appartenenza. Nelle attuali condizioni, possiamo realizzare la richiesta nel modo seguente:

using (TECHNETEntities db = new TECHNETEntities())
{
    var art = db.Articoli
                .Join(db.Famiglie, 
                      articolo => articolo.CodFamiglia, 
                      famiglia => famiglia.CodFamiglia,
                      (articolo, famiglia) => new  { Articoli = articolo, Famiglie = famiglia})
                .Where((x) => x.Articoli.CodArt == "ART005")
                .FirstOrDefault();
 
    MessageBox.Show(art.Articoli.DesArt + " - " + art.Famiglie.DesFamiglia);
}

Istanziato come prima l'oggetto TECHNETEntities, dichiariamo un tipo questa volta implicito. Esso conterrà il risultato dell'interrogazione LINQ che si snoderà sulle seguenti condizioni: Selezione da db.Articoli, eseguendo un Join con db.Famiglie, sulla base dei rispettivi campi CodFamiglia, ed estraendo da tale Join un nuovo oggetto che contenga le proprietà di entrambi. Su questo oggetto viene eseguito il metodo Where, accedendo alla classe Articoli ed effettuando la selezione sul campo CodArt, per poi restituire il primo elemento dell'interrogazione.

La nostra variabile art è ora un tipo composto, e potremo accedere rispettivamente alle proprietà della parte Articoli e della parte Famiglie: si noti come nella MessageBox venga emessa la descrizione dell'articolo, così come quella della famiglia. Eseguendo un test nel ricercare il prodotto ART005, il risultato mostrato è il seguente:

Se però si fosse tenuto conto di tale vincolo fin dalla fase progettuale, inserendo delle Foreign Keys ove opportuno, EF avrebbe potuto accorgersene, andando a creare questo tipo di relazione in modo automatico. Eseguiamo allora una variazione sulla struttura di database, per vedere come questo incida nella modellazione delle classi.

Utilizzando per praticità SQL Management Studio, andiamo ad inserire, nella tabella Articoli, una Foreign Key che stabilisca la relazione tra Articoli e Famiglie

Una volta salvate le modifiche sul database, lato Visual Studio dovremo rigenerare il modello dati. Sarà sufficiente aprire il file TechnetModello.edmx, che riporta graficamente il nostro modello dati, e tramite click destro selezionare la voce «Update Model from Database».

Seguirà un brevissimo wizard, nel quale semplicemente ci limiteremo a confermare l'operazione di refresh.
Vedremo quindi che il nostro modello verrà aggiornato stabilendo una relazione tra le due tabelle, che scopriamo - in ambito EF - essere chiamate «Navigation Properties».

Questo passaggio va a modificare le nostre classi in maniera importante. La classe Famiglie acquisisce una nuova proprietà, Articoli, di tipo ICollection<Articoli>, mentre la classe Articoli ha ora una proprietà Famiglie, di tipo Famiglie, dichiarata virtual.

Ciò significa che nel nostro modello dati, le due entità saranno naturalmente relazionate tra loro. In questo caso, se ad esempio volessimo eseguire nuovamente l'interrogazione di prima, su descrizione articolo e descrizione famiglia, non dovremo istruire direttamente il compilatore su come le tabelle siano legate, ma potremo fare riferimento alle proprietà allestite da EF.
Tradotto nella pratica, l'ultimo esempio diventa:

using (TECHNETEntities db = new TECHNETEntities())
{
      Articoli art = db.Articoli.Where((x) => x.CodArt == "ART005").FirstOrDefault();
 
      MessageBox.Show(art.DesArt + " - " + art.Famiglie.DesFamiglia);
}

In altre parole, la classe Articoli contiene già la proprietà Famiglie, derivato da database. Di conseguenza, possiamo interrogare direttamente Articoli andando a selezionare le proprietà in essa desiderate, sapendo però che troveremo il essa la proprietà Famiglie, che risolverà autonomamente il Join esistente tra le due entità, ed alla quale potremo accedere direttamente dalla variabile di tipo Articoli.

Code First

In questa sezione, vedremo una modalità di Code-First utilizzabile in caso di database presente, lasciando per un prossimo articolo l'approccio che va a definire la base dati da zero (o quasi). Il progetto che trovate nella sezione Download si riferisce a questa seconda modalità, considerando appunto che il Code-First (indipendentemente dal fatto che parta da un modello predefinito, o che lo definisca completamente) agisce con priorità rispetto alla base dati fisica, ed il lettore potrà quindi generarla sulla propria istanza di SQL Server. Per maggior praticità, è stata creata una cartella di nome Model, che conterrà le classi inizialmente create da EF, ma che potremo variare per modificare il modello.

Aggiungiamo quindi nuovamente un oggetto di tipo ADO.NET Entity Data Model, che chiameremo «TechnetModelloCF». I passaggi da compiere sono simili a quelli già visti in precedenza, con la sola eccezione della modalità da selezionare, che in questo caso sarà «Code-First from Database». Proseguiamo poi i restanti passaggi come già fatto nel caso di Database-First.

Definizione classi

Come vedremo, non vi saranno rappresentazioni grafiche delle entità o delle loro relazioni, e le classi generate a partire dalle nostre tabelle risulteranno sensibilmente differenti. Vediamole in dettaglio, partendo da una classe che nel caso di Database-First non era così immediatamente visibile. 

namespace ArticoloEF.Model
{
    using System;
    using System.Data.Entity;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Linq;
 
    public partial  class TechnetModelloCF : DbContext
    {
        public TechnetModelloCF()
            : base("name=TechnetModelloCF")
        {
        }
 
        public virtual  DbSet<Articoli> Articoli { get; set; }
        public virtual  DbSet<Famiglie> Famiglie { get; set; }
 
        protected override  void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Famiglie>()
                .HasMany(e => e.Articoli)
                .WithRequired(e => e.Famiglie)
                .WillCascadeOnDelete(false);
        }
    }
}

Questa è la classe TechnetModelloCF, la classe che riporta il nome che abbiamo assegnato al modello, e che va ad ereditare il proprio tipo e funzionalità dalla classe DbContext di EF. Il compito di questa classe è principalmente quello di rendere disponibili le entità di database, ma anche di stabilire parametri particolari di generazione della base dati, nonché di esplicitare, se richieste, le relazioni tra i vari oggetti.

Nel nostro caso, notiamo come anzitutto sia presente un costruttore, a cui viene passato il parametro "name=TechnetModelloCF". Il costruttore si preoccuper�� di stabilire la connessione dal database quando richiesto, e ciò avverrà tramite il parametro passato, che può essere direttamente la ConnectionString oppure, come nell'esempio, un nome che si riferisce ad una ConnectionString definita nel file App.config.

Successivamente, vengono definite le entità a cui si dovrà avere accesso (le nostre tabelle), le quali saranno oggetti di tipo DbSet, definiti sulla base delle rispettive classi. Sono altresì dichiarate virtual, onde permettere l'accesso fisico ai dati memorizzati. In ultimo, in quello che è l'override del metodo OnModelCreating (in cui definire le regole di creazione del modello) viene esplicitata la relazione tra le due entità. Più nello specifico, viene indicato nel modelBuilder come sia presente un'entità Famiglia, la quale è vincolata ad Articoli in un legame molti-ad-uno, e che non deve eseguire operazioni di cancellazione in cascata se sulla tabella principale vengono eliminate referenze a quella secondaria.

Vediamo quindi le restanti due classi.
Partiamo da Articoli.cs:

namespace ArticoloEF.Model
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.Spatial;
 
    [Table("Articoli")]
    public partial  class Articoli
    {
        [Key]
        public int IdArticolo { get; set; }
 
        [Required]
        [StringLength(25)]
        public string  CodArt { get; set; }
 
        [Required]
        [StringLength(50)]
        public string  DesArt { get; set; }
 
        [Required]
        [StringLength(6)]
        public string  CodFamiglia { get; set; }
 
        public virtual  Famiglie Famiglie { get; set; }
    }
}

In effetti, non c'è molta differenza rispetto a quella generata in Database-First, eccezion fatta per alcune indicazioni, specificate tra parentesi quadre, che precedono i vari campi. Si tratta delle cosiddette DataAnnotations, elementi molto utili e concisi che permettono una grande flessibilità nel dichiarare particolari aspetti dei dati che andremo a creare. Non si tratta di istruzioni obbligatorie, ma al tempo stesso possono essere necessarie per modellare con maggiore precisione le nostre entità.

Nella classe di esempio, si nota come vi sia un'annotazione Table, dichiarata prima della classe. Essa specifica, su database, quale sia il nome della tabella cui fare riferimento. Se non indicata, la tabella assumerà il nome della classe (ed è sicuramente il comportamento più normale, ma possono esistere casi in cui si debba optare per un nome di classe diverso da quello di tabella). Sui campi, vediamo tre annotazioni particolari: Key, anteposta al campo IdArticolo, istruirà il modello quanto alla chiave primaria di tabella, appunto IdArticolo. Required richiede l'obbligatorietà del campo (traduce l'istruzione T-SQL NOT NULL), mentre StringLength specifica la massima lunghezza che il campo in questione deve avere.

Con i dovuti distinguo, lo stesso tipo di annotazione è utilizzato anche nella classe Famiglie.cs:

namespace ArticoloEF.Model
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Data.Entity.Spatial;
 
    [Table("Famiglie")]
    public partial  class Famiglie
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public Famiglie()
        {
            Articoli = new  HashSet<Articoli>();
        }
 
        [Key]
        [StringLength(6)]
        public string  CodFamiglia { get; set; }
 
        [Required]
        [StringLength(50)]
        public string  DesFamiglia { get; set; }
 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual  ICollection<Articoli> Articoli { get; set; }
    }
}

In questo caso, si noterà un costruttore di classe aggiuntivo, che inizializza la proprietà definita come ICollection<Articoli> sulla base della classe HashSet, particolarmente performante nell'eseguire operazioni su lista.

Modifica classi e migrazioni

Prima di fare qualche breve esempio di accesso ai dati tramite questa modalità, potremmo desiderare di vedere - dato appunto il contesto Code-First - cosa accade se modifichiamo il modello.
Supponiamo allora, molto semplicemente, di voler aggiungere un campo alla tabella Articoli. Inseriremo un campo di nome CostoStandard, di tipo Decimal.

Che cosa accade se cerchiamo di eseguire ora l'applicazione? Dopo qualche secondo di attesa, verrà sollevata un'eccezione, che ci avviserà della differenza tra il nostro modello dati e quello a cui stiamo cercando di accedere. Nel caso specifico, la colonna CostoStandard non risulta essere un campo valido, ed in effetti non è presente su database.

Introduciamo qui il concetto di migrazione: EF è in grado, a partire dalle nostre classi, ed utilizzando un database come confronto, di stabilire quali modifiche sia necessario apportare a quest'ultimo, per renderlo compatibile con il modello impostato a programma. Iniziamo quindi con il predisporre il nostro progetto alla migrazione. Questo tipo di operazioni si svolge utilizzando la Package Manager Console (Tools » Nuget Package Manager » Package Manager Console), che rende disponibili diverse cmdlet Powershell, tra le quali quelle che andremo ad usare.

Per abilitare il progetto alle migrazioni, è necessario digitare:

Enable-Migrations

Si nota qui come EF vada a verificare se vi è un database sul quale eseguire un primo raffronto. Il comando creerà nella directory del progetto una cartella di nome Migrations, che inizialmente conterrà il solo file Configuration.cs. Questo file è utile se desideriamo ad esempio distribuire dei dati di partenza: supponendo il caso di costruzione da zero di un database, o di introduzione di una tabella contenente record predistribuiti, potremo crearli - a migrazione conclusa - tramite il metodo Seed, oggetto di un futuro articolo.

Tornando in argomento, a questo punto il nostro progetto è predisposto per le migrazioni, ma non ne contiene ancora alcuna. Le migrazioni possono essere create con il comando:

Add-Migration <NOME_MIGRAZIONE>

dove <NOME_MIGRAZIONE> è ovviamente un testo digitato dallo sviluppatore, che identifica quel particolare rilascio di migrazione.

Qui ho richiesto la creazione di una migrazione, dal nome CostoStandardSuArticoli. Noterete la creazione di un file di classe nella cartella Migrazioni (la cui classe estende il tipo DbMigration), dal nome composto dal timestamp corrente, più il nome che avremo digitato. Nell'immagine a seguire, si nota il contenuto del file creato, in cui vediamo una serie di istruzioni atte a modellare la base dati secondo quanto definito nelle nostre classi modificate. Vediamo quindi che nelle istruzioni di creazione della tabella Articoli, è presente il nuovo campo CostoStandard.

È buona norma, prima di creare migrazioni personalizzate, creare una migrazione iniziale di startup, mediante l'istruzione:

Add-Migration InitialCreate –IgnoreChanges

Ciò permetterà di spostare completamente in ambito Code-First il controllo della modellazione dei dati, che nel contesto attuale abbiamo ancora derivato da un database esistente.

A questo punto non resta che lanciare le migrazioni così create, ed osservare il risultato.
Tale operazione viene compiuta mediante l'istruzione

Update-Database

Ad operazione terminata, visualizziamo lo schema della tabella Articoli mediante Management Studio, per vedere creata la nuova colonna.

Download 

Al seguente link, è possibile scaricare il progetto Code-First utilizzato nell'esempio: https://code.msdn.microsoft.com/Introduction-to-Entity-09676091

Per utilizzare correttamente il codice sorgente, dovrà essere modificata l'istanza SQL Server di connessione, come specificata nel file App.config del progetto, e su tale istanza si dovrà creare un database di nome TECHNET.

Conclusione

In questa prima parte abbiamo scalfito la superficie di Entity Framework, cercando di capire cosa sia, come possa venirci in aiuto, e quali sono i paradigmi utilizzabili nel suo impiego. Abbiamo visto qualche banale esempio dell'uso degli oggetti creati da database, ed iniziato ad accennare ai meccanismi di migrazione. Si consiglia al lettore di prendere confidenza con queste prime nozioni, in attesa di proseguire in questa panoramica, con i prossimi articoli della serie.

Altre lingue

Il presente articolo è disponibile nelle seguenti localizzazioni:

Articoli in questa serie