I nomi di questi progetti hanno fatto un po' di confusione ma in fondo stiamo parlando di quella che oggi è nota come "Entity Framework 4.1"
La minor relase di EF4 vede in primo piano il rilascio delle caratteristiche di "Code-First" che sono state rese pubbliche in "beta" in diverse Community Technology Preview (CTP).
Personalmente ho sviluppato diverso codice basato sulla versione CTP4 di Code-First che mi ha subito entusiasmato. La versione CTP5 ha portato diverse migliorie ma purtroppo ha anche tolto (temporaneamente) alcune caratteristiche che non mi hanno permesso di migrare a questa versione.
Nel weekend ho migrato il mio codice più grosso alla versione Release Candidate (che è da considerarsi finale per quello che riguarda la compatibilità con la versione RTM) e sono emerse tante piccole cose dovute in parte al refactoring ed in parte al cambio delle convention del database creato automaticamente da Code-First.
Andiamo al sodo …
Parte 1. Refactoring
- Togliere le reference Microsoft.Data.Entity.CTP
- Aggiungere al posto la reference EntityFramework.DLL (EntityFramework)
- "using System.Data.Entity;" rimpiazza "using System.Data.Entity.Infrastructure" e "using System.Data.Entity.ModelConfiguration;"
- Classi rinominate nella versione RC (maggiori info su questo nel team blog)
CTP4 è CTP5 |
Database | System.Data.Entity.Database.DbDatabase |
RecreateDatabaseIfModelChanges | System.Data.Entity.Database.DropCreateDatabaseIfModelChanges |
AlwaysRecreateDatabase | System.Data.Entity.Database.DropCreateDatabaseAlways |
CreateDatabaseIfNotExists | System.Data.Entity.Database.CreateDatabaseIfNotExists |
SqlConnectionFactory | System.Data.Entity.Database.SqlConnectionFactory |
SqlCeConnectionFactory | System.Data.Entity.Database.SqlCeConnectionFactory |
CTP5 è RC |
DBDatabase | Database |
ModelBuilder | DBModelBuilder |
Da notare che anche molti metodi/proprietà sono state rinominate (ModelMatchesDatabase => CompatibleWithModel, DeleteIfExists => Exists + Delete) ma sono decisamente intuitivi.
Parte 2. Mapping Modello
Lunghezza di default delle stringhe. È cambiata da 4000 (attenzione non max ma 4000) a 128.
Per ripristinare il valore di default è sufficiente rimuovere la "Convention" che imposta il default a 128:
modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PropertyMaxLengthConvention>();
Alternativamente è possibile impostare, proprietà per proprietà, la lunghezza del campo indicando .HasMaxLength(4000) nella fluent API che modella le proprietà.
Altri cambiamenti nella modellazione
IsIdentity |
CTP4 | modelBuilder.Entity<table>().Property(e => e.Integer).IsIdentity(); |
RC | modelBuilder.Entity<table>().Property(e => e.Integer). HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); |
HasStoreType |
CTP4 | HasStoreType |
RC | HasColumnType |
DbContext.ObjectContext |
CTP4 | this.ObjectContext.ObjectStateManager…. |
RC | var ctx = ((System.Data.Entity.Infrastructure.IObjectContextAdapter)this).ObjectContext; ctx.ObjectStateManager…. |
Attenzione. L'errore "The property 'xyz' is not a declared property on type 'abc' …" è un errore di validazione per cui si è provato a mappare le proprietà per una classe derivata che sono definite in realtà nella classe base.
Comparazione Schema DB. A questo punto ho iniziato a comparare lo schema del vecchio DB con quello nuovo, grazie al potente "SQL Compare" di Red-Gate.
La convenzione dei nomi per le tabelle e delle colonne relative alle relazioni esterne sono cambiate. Per esempio prima i nomi contentevano un "_" (underscore).
Stiamo parlando delle relazioni esterne che sono modellate con WithMany nella fluent API. Per conservare i nomi vecchi, è necessario esplicitarli:
.WithMany().Map(mc =>
{
mc.ToTable("Nome_custom_della_tabella");
mc.MapLeftKey("tabella_Id");
mc.MapRightKey("external_Id");
});
Personalmente questa soluzione non mi piace perché rende inutilmente troppo verboso il codice che descrive il modello.
La seconda soluzione è quindi di migrare i dati al nuovo schema, cosa che vedremo a breve in questo post.
Parte 3. Database
Probabilmente è possibile rimappare tutto al vecchio schema del database. Personalmente preferisco aggiornare anche quello e con uno sforzo non eccessivo ci sono riuscito.
Ecco i passi che ho seguito.
Il wizard SSIS di Sql Server ha il grave difetto di non considerare i constraint sul database. Per cui durante il trasferimento dati avremo una serie di errori dovuti alla violazione dei constraint stessi. Per evitare di farci tutto a mano, la soluzione più semplice è di disabilitare i constraint per poi riabilitarli a fine migrazione.
Queste poche righe generano gli script SQL per:
- disabilitare i constraint
(a questo punto dovremo avviare il wizard SSIS) - abilitare i constraint
- eseguire il check dei constraint sulle righe trasferite. Questo è uno step indispensabile affinché i constraint siano 'trusted'
- eseguire un check finale (è solo una cautela)
set nocount on
select '-- disable'
select 'ALTER TABLE ' + name + ' NOCHECK CONSTRAINT ALL' from sys.tables
select '-- enable'
select 'ALTER TABLE ' + name + ' CHECK CONSTRAINT ALL' from sys.tables
select '-- check existing rows'
select 'ALTER TABLE ' + name + ' WITH CHECK CHECK CONSTRAINT ALL' from sys.tables
select '-- final test'
select 'DBCC CHECKCONSTRAINTS (' + name + ')' from sys.tables
set nocount off
Uso del wizard SSIS per traferire i dati
Dopo aver selezionato dal Sql Management server il vecchio DB, selezionare All Task, Export e avviare il wizard indicando db sorgente e destinazione.
Il DB di destinazione deve già essere naturalmente stato creato (vuoto) da EF4.1 RC.
- Mappare tutte le tabelle
- Escludere la tabella EdmMetadata (qui si trova l'hash relativo allo schema del db, quindi non deve essere copiato l'hash vecchio)
- Selezionare "Enable Identity Insert" sui mapping che hanno colonne di tipo int con "identity" abilitata
(In pratica copia anche il valore intero originale invece di mantenere quello creato dal database sulla tabella di destinazione. Questo passo è indispensabile). - Le tabelle con ForeignKey sono chiamate in modo diverso (senza underscore). Anche le colonne di queste tabelle vanno mappate manualmente perché hanno un nome differente.
(questo è lo step alternativo a "Comparazione schema DB" che abbiamo visto prima)
Un altro punto importante è quello di disabilitare gli errori di truncation dei campi. Infatti le colonne chiamate "Discriminator" utilizzate per discernere i nomi delle classi base e derivate sono ora create di 128 caratteri invece che 4000. Ritengo che sia una lunghezza più che sufficiente per un nome di classe e perció non ho preso in considerazione la modifica nel modello della loro lunghezza (cosa assolutamente possibile).
Abbiate l'accortezza di salvare il package di trasferimento dati. Attenzione che se lo salvate su file non è più rieditabile con lo stesso wizard (cosa che trovo assurda). I file dtsx vanno rieditati con il Business Intelligence Development Studio (pusalnte destro sul file, edit). Pazzesco ma non ho trovato il modo di rieditarlo con lo stesso wizard con cui è stato creato.
Fatto questo, il mio progetto ha funzionato egregiamente e usa ora il nuovo database. Migrazione non semplice ma spero che possa essere di aiuto a qualcuno.
Buon lavoro.