posts - 644, comments - 2003, trackbacks - 137

My Links

News

Raffaele Rialdi website

Su questo sito si trovano i miei articoli, esempi, snippet, tools, etc.

Archives

Post Categories

Image Galleries

Blogs

Links

Migrazione dei progetti "Code-First" (CTP4/5) alla versione finale

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.

Print | posted on lunedì 21 marzo 2011 11:45 |

Feedback

Gravatar

# re: Migrazione dei progetti "Code-First" (CTP4/5) alla versione finale

Nulla di nuovo rispetto a quando ci siamo parlati.
Il problema sta nel fatto che la fluent API deve poter accedere alle proprietà, quindi il modellatore deve avere quella visibilità.
Le soluzioni non sono l'ottimale ma possono essere riassunte in:
- DM e modellatore di EF nello stesso assembly, definendo quelle navigation property come internal
e, sicuramente meglio:
- DM e modellatore in due assembly differenti, navigation property internal e attributo internalsvisibleto all'assembly dove è presente il modelbuilder.
Poi da queste soluzioni possiamo inventarne una serie di soluzioni derivate ...
21/03/2011 15:45 | Raffaele Rialdi
Gravatar

# re: Migrazione dei progetti "Code-First" (CTP4/5) alla versione finale

@Alk. Il team ha volutamente scartato tutte le ipotesi di non-strongtyped in quanto la gran parte del feedback in tal senso è stato negativo (ti riporto quanto mi è stato detto).
In parte sono daccordo con questa scelta anche perché se vai per stringhe salta il refactoring e comunque accedere via reflection a proprietà protected/private è da evitarsi.
Il problema reale è che non vedo una soluzione che sia semplice, strong-typed e permetta un totale disaccoppiamento.
21/03/2011 16:45 | Raffaele Rialdi
Gravatar

# re: Migrazione dei progetti "Code-First" (CTP4/5) alla versione finale

In scenari DDD è classico avere in una entità la collection ai figli privata e permettere dal di fuori di manipolare con metodi, è la base degli AGGREGATES. Il mapping di proprietà private serve poco, ma quando serve, secondo me serve :).

Cmq ho già provato, con qualche riga di codice si può fare e quindi buonanotte al sacco, posso mappare proprietà private anche in EF 4.1 e quindi sono contento lo stesso :D

alk.
21/03/2011 17:36 | Gian MAria
Gravatar

# re: Migrazione dei progetti "Code-First" (CTP4/5) alla versione finale

@Alk, Matteo. Come scrivevo ci sono tante possibili soluzioni/varianti. Ma non sono soluzioni generali e dipendono molto dal contesto in cui le applichi.
L'interfaccia esplicita è papabile ma non ho fatto prove. Bisogna vedere come funziona internamente il loro model builder, o più facilmente fare una prova.
Il mapping di proprietà private è la soluzione che ti toglie di mezzo i problemi di dipendenze ma è "fragile" al refactoring. Buono cmq per alcune occasioni.
La soluzione internal ha un legame più forte ma in alcuni casi dove il tuo datalayer sai che non cambierà mai, può funzionare alla grande.

Alk che ha seguito la mia sessione di pochi giorni fa su C# 4.0 può intuire meglio che anche il disaccoppiamento via type embedding può risolvere egregiamente il problema. Macchinoso ma ti toglie *tutti* i problemi di cui sopra.
In pratica si creano due modelli equivalenti dal punto di vista del CLR ma in cui quello destinato al largo uso ha proprietà private mentre quello destinato al modelbuilder ce le ha pubbliche. Il tutto parte dalla stessa codebase e quindi è assolutamente a prova di refactoring.

Context does matter! :)
21/03/2011 19:37 | Raffaele Rialdi
Gravatar

# re: Migrazione dei progetti "Code-First" (CTP4/5) alla versione finale

Provo l'interfaccia esplicita perchè fino ad ora è solo un'idea. Grazie per l'approfondimento Raf :).

Context does matter! :), "un'immagine" vale più di mille parole! :D
22/03/2011 13:10 | Matteo Migliore
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET