Jucandu-ma cu EF4.1, edmx si sql server compact.

Sumar: In acest blog veti afla despre customizarea codului generat de fisiere .tt pornind de la“Database First” . Veti genera cod care va fi bun pentru accesat SqlServer 2005 si SqlServer Compact – precum si care pare sa fie varianta cistigatoare intre Attribute si codul scris pentru definit relatii in CodeFirst.

Cuprins:

De cind a aparut EF mi-a parut ingrozitor ca nu am optiunea simpla de a face deliver la un soft cu o Baza de date linga ea. Inainte, cu VB6 , aveam Access … dar EF nu stia Access. Varianta cistigatoare parea sa fie NHibernate – dar sunt satul sa scriu fisiere de configurare pornind de la baze de date( faceam asta in VB5 cu un generator …)

Asa ca am stat pe EF pina la versiunea 4.1 – care a venit cu suport automat pentru SqlServerCompact 4.0. Asa ca, daca tot aveam de facut aplicatia de chat ( work in progress pe blogul tehnic in EN, http://msprogrammer.serviciipeweb.ro/category/mvc-messaging-system/ , si pe codeplex ), am zis ca sa incep sa folosesc. Am generat baza de date( vezi aici si aici) si am instalat SqlServerCompact 4.0 si ultimul SP pentru VS 2010.

Am creat un proiect care sa aiba fisierul edmx si am generat codul cu extensia pentru EF4.1 code first( click dreapta pe edmx, add code generation item , selectati ADO.NET DbContext generator)

clip_image002

Ei, si aici a inceput nebunia : codul generat de template-ul de EF4.1 code first era minimal . Super minimal. Ingrozitor de minimal.

Sa va arat:

public partial class smsg_MessageThread

{

public long IDMessageThread { get; set; }

public long IDMessageInitial { get; set; }

public long IDMessageReply { get; set; }

public System.DateTime DateInserted { get; set; }

public virtual smsg_Message smsg_Message { get; set; }

public virtual smsg_Message smsg_Message1 { get; set; }

public virtual smsg_Message_Archive smsg_Message_Archive { get; set; }

public virtual smsg_Message_Archive smsg_Message_Archive1 { get; set; }

} 

Care credeti ca e Primary Key? Da , intr-adevar, IDMessageThread – dar cum recunoaste EF Code First? ( Raspuns : automat – vedeti http://blogs.msdn.com/b/adonet/archive/2011/03/15/ef-4-1-code-first-walkthrough.aspx )

Nu mai pun la socoteala uritenia de smsg_Message si smsg_Message1, corespondente la IDMessageInitial si IDMessageReply – care e cu care ?

Oricum , nu mi-a placut . Asa ca am citit despre atributele Key si ForeignKey – si am inceput sa modific template-ul .tt.

(Cum ? Citind la greu. De exemplu, uitati cum arata in Winmerge diferenta intre fisierul initial si cel modificat:

clip_image004

)

Si am obtinut asta:

public partial class smsg_MessageThread

{

[Key]

public long IDMessageThread { get; set; }

public long IDMessageInitial { get; set; }

public long IDMessageReply { get; set; }

public System.DateTime DateInserted { get; set; }

[ForeignKey("IDMessage")]

public virtual smsg_Message smsg_Message { get; set; }

[ForeignKey("IDMessage")]

public virtual smsg_Message smsg_Message1 { get; set; }

[ForeignKey("IDMessageArchive")]

public virtual smsg_Message_Archive smsg_Message_Archive { get; set; }

[ForeignKey("IDMessageArchive")]

public virtual smsg_Message_Archive smsg_Message_Archive1 { get; set; }

} 

Un pic mai bine ,corect ? Are deja

[Key]

public long IDMessageThread { get; set; } 

Mai are


[ForeignKey("IDMessage")]

public virtual smsg_Message smsg_Message { get; set; } 

Dar cum ii pot specifica cu care tabela face FK ? Si cu ce cimp din cele doua ?

Asa ca am trecut la faza a doua – generarea din Context a relatiilor intre obiecte. Iarasi modificare la greu – dar de data aceasta fisierul .tt care genereaza contextul. Versiunea oficiala protejeaza cit poate ea de bine in stilul MSFT pe programatorii incepatori:

protected override void OnModelCreating(DbModelBuilder modelBuilder)

{

throw new UnintentionalCodeFirstException();

} 

Am modificat si , dintr-un fisier de 194 de linii, am ajuns la unul cu 281 de linii ( am scris aproape 100 de linii in generator, nu in cod ..)

Am obtinut aceasta :

modelBuilder.Entity<smsg_MessageThread>().ToTable(prefix + “smsg_MessageThread”);

modelBuilder.Entity<smsg_MessageThread>().HasKey(item => item.IDMessageThread);

modelBuilder.Entity<smsg_MessageThread>()

.HasRequired(item => item.IDMessage_IDMessageInitial)

.WithMany(u => u.IDMessage_IDMessageInitial)

.HasForeignKey(x => x.IDMessageInitial)

.WillCascadeOnDelete(false);

modelBuilder.Entity<smsg_MessageThread>()

.HasRequired(item => item.IDMessage_IDMessageReply)

.WithMany(u => u.IDMessage_IDMessageReply)

.HasForeignKey(x => x.IDMessageReply)

.WillCascadeOnDelete(false);

modelBuilder.Entity<smsg_MessageThread>()

.HasRequired(item => item.IDMessageArchive_IDMessageInitial)

.WithMany(u => u.IDMessageArchive_IDMessageInitial)

.HasForeignKey(x => x.IDMessageInitial)

.WillCascadeOnDelete(false);

modelBuilder.Entity<smsg_MessageThread>()

.HasRequired(item => item.IDMessageArchive_IDMessageReply)

.WithMany(u => u.IDMessageArchive_IDMessageReply)

.HasForeignKey(x => x.IDMessageReply)

.WillCascadeOnDelete(false); 

Sa le luam pe rind:

1.

modelBuilder.Entity<smsg_MessageThread>().ToTable(prefix + “smsg_MessageThread”); 

Imi ofera ocazia de a schimba numele tablei din care sa ia datele –prefix il pot pune intr-un fisier de configurare.

2.

 modelBuilder.Entity<smsg_MessageThread>().HasKey(item => item.IDMessageThread); 

Scrie clar cine e PK ( inca nu scrie ca e identity – va las pe voi sa modificati .tt)

3.


modelBuilder.Entity<smsg_MessageThread>()

.HasRequired(item => item.IDMessage_IDMessageInitial)

.WithMany(u => u.IDMessage_IDMessageInitial)

.HasForeignKey(x => x.IDMessageInitial)

.WillCascadeOnDelete(false); 

Ei, aici e mai lung: zice ca lui IDMessageInitial ii corespunde colectia IDMessage_IDMessageInitial . Si ca trebuie sa existe si ca nu face cascade on delete.

E mai bine , nu-i asa?

Ultima problema la fisierele .tt

De cite ori sunt modificate , genereaza cod si suprascriu fisierul generat. Deci, daca facem modificari in fisierul .cs, atunci nu mai trebuie sa modificam fisierul .tt ( sau sa integram modificarile ) . Oricare din variante e proasta. Solutia : partial methods. Ce ar fi daca as scrie codul generat de catre .tt in alt fisier si l-as apela (daca e nevoie) din functia care creaza modelul ? Cod, va rog:


partial void OnBeginModelCreating(DbModelBuilder modelBuilder, string prefix,ref bool Continue);

protected override void OnModelCreating(DbModelBuilder modelBuilder)

{

bool Continue=true;

OnBeginModelCreating(modelBuilder,prefix, ref Continue);

if(!Continue)

return;

modelBuilder.Entity<smsg_MessageThread>().ToTable(prefix + “smsg_MessageThread”);

// si celelalte. 


Sa o luam de la inceput :

partial void OnBeginModelCreating

procedura asta se creaza doar daca exista intr-un alt fisier cu partial class la clasa din care face parte procedura. Daca nu exista, nu se creeaza ! Exemplu:

bool Continue=true;

OnBeginModelCreating(modelBuilder,prefix, ref Continue);

if(!Continue)

return;

devine :

bool Continue=true;

if(!Continue)

return;

( Hint : a disparut linia cu procedura )

Daca exista, voi intoarce Continue la false ( doar e argument ref !) si nu se va mai executa codul de dupa ( adicatelea

modelBuilder.Entity<smsg_MessageThread>().ToTable(prefix + “smsg_MessageThread”);

// si celelalte 

Solutia imi pare frumoasa ….dar nu stiu daca si voua.

Acum cum schimbam baza de date: in fisierul de configurare putem pune pentru SqlServer:

<add name=”MessagingDB” connectionString=”Data Source=(local)\sqlexpress;Initial Catalog=SMsgS1;Persist Security Info=True;User ID=smsg;Password=smsg;MultipleActiveResultSets=True” providerName=”System.Data.SqlClient” />

Sau pentru SqlCompact:

<add name=”MessagingDB” connectionString=”Data Source=|DataDirectory|\msg.sdf” providerName=”System.Data.SqlServerCe.4.0″ />

Si EF o sa fie bucuros sa creeze fisierul sdf pentru noi(cu relatii cu tot..)

Incheiere:

Am vorbit despre EF 4.1, despre generare codului cu fisierele .tt ,partial methods precum si despre faptul ca e mai bine sa va faceti configurarea EF4.1 din ModelBuilder , decit din atribute.

Puteti downloada cele 3 (x 2 , context + model ) fisiere .tt

Versiunea VS

Prima versiune

A doua versiune

si sa va jucati cu un edmx. Nu uitati

1. sa nu le puneti pe toate 3 deodata ( modelul generat o sa fie comun si VS o sa se enerveze)

2. sa schimbati in fisierul .tt numele fisierului edmx cu numele fisierului vostru.

PS: Daca va pasioneaza subiectul, veniti la RONUA pe 19 oct. 2011. O sa dezvolt pe larg….

3 thoughts on “Jucandu-ma cu EF4.1, edmx si sql server compact.

  1. Pai nu tocmai asta era ideea la code-first? Sa incepem cu scrierea (manuala) a claselor din domain model (sau clase POCO, dupa gust), dupa care sa creeam db-ul sau maparile (eventual automat), pornind de la clasele scrise de manutza – NHibernate style..

    In felul asta scapam de generatoare de cod care trebuie dupa aia customizate, si ne concentram pe domain model in loc de DB (care e vazut doar ca un persistence concern, care poate fi abordat mai tarziu)..
    Normal, e o schimbare majora in stilul de lucru, da’ ..

  2. Mda… Daca incep cu scrierea manuala si apoi db-ul fac 2 munci( pentru ca , daca il las in pace pe generator-ul default, o sa faca fericit nvarchar(max) )
    Asa ( in stilul prezentat in post) muncesc 1 singura data.

  3. Aha, si asta e adevarat.. Pentru chestii netriviale tot trebuie customizat ce se genereaza, fie intr-o directie, fie in alta, sau facut totul manual..

Leave a Reply to Tudor Cancel reply

Your email address will not be published. Required fields are marked *