Pentru PMKB, http://pmkb.codeplex.com/ am dorit sa muncesc cit mai usor. Si, pentru ca adopt o strategie gen Table per Type am considerat ca designul bazei de date este cel al claselor.
Daca va uitati la proiectul PMKB_DAL o sa vedeti urmatoarele fisiere .tt
- Model1.Context.tt – genereaza EFContext si genereaza codul pentru Fluent API, ca de exemplu:
modelBuilder.Entity<Link_Tag>().ToTable(HelperTables.NameTable(Tables_PMKBEntities.Link_Tag)); modelBuilder.Entity<Link_Tag>().HasKey(item => item.LinkTagID); modelBuilder.Entity<Link_Tag>() .HasRequired(item => item.Link_LinkID_Link_Tag_LinkID) .WithMany(u => u.Link_LinkID_Link_Tag_LinkID) .HasForeignKey(x => x.LinkID) .WillCascadeOnDelete(false);
- Model1.tt – genereaza codul pentru fiecare clasa + o interfata pentru ea:
public interface I_Link { long LinkID{get;set;} string LinkName{get;set;} string LinkDescription{get;set;} string LinkUrl{get;set;} Nullable<long> CreationUserID{get;set;} Nullable<System.DateTime> LinkCreationDate{get;set;} string LinkShortURL{get;set;} } public partial class Link: FastDatabaseQuery.IReceiveVisitor, I_Link {
- ITList.tt – genereaza codul pentru clasa Lista – pentru fiecare tabela pot sa vreau sa incarc mai multe rinduri sau sa sterg ceva rapid:
[System.Diagnostics.DebuggerDisplayAttribute("Link_List Count={Count}")] public partial class Link_List : ColList<Link> { //code public void Delete_LinkID(long value) { base.Conection.Database.ExecuteSqlCommand("delete from " + HelperTables.NameTable(Tables_PMKBEntities.Link) + " where LinkID = {0}",value); }
Astfel incit codul devine:
using (Link_List ll = new Link_List(ConnectionName)) { if (string.IsNullOrEmpty(OrderBy)) OrderBy = Link_List.FieldNames.LinkID; var iq = ll.LoadFromDB.LoadAllQ(); var ordered = Link_FindDB.OrderByField(iq, OrderBy); if (!string.IsNullOrEmpty(Search)) { ll.LoadFromDB.AddToCustomPredicate(Link_FindDB.fexp_LinkUrlContainsMultipleDef(Search), false); ll.LoadFromDB.AddToCustomPredicate(Link_FindDB.fexp_LinkDescriptionContainsMultipleDef(Search), false); ll.LoadFromDB.AddToCustomPredicate(Link_FindDB.fexp_LinkNameContainsMultipleDef(Search), false); ordered = ll.LoadFromDB.LoadFindCustomPredicateOrderedQ(OrderBy); } return ordered.ToPagedList(pageNumber, PageSize); }
- Find.tt – genereaza codul pentru cautare in BD ( de exemplu, pentru date pot sa am between) . IN acest exemplu arat fexp_LinkUrlContainsMultipleDef care face Like din Sql :
public static Func<string, Expression<Func<Link, bool>>> fexp_LinkUrlContainsMultipleDef = value => fexp_LinkUrlContainsMultiple(value, "%"); public static Func<string,string, Expression<Func<Link, bool>>> fexp_LinkUrlContainsMultiple = (value,separator) => { value=value.ToLower(); var arr=value.Split(new string[1]{separator}, StringSplitOptions.RemoveEmptyEntries); switch(arr.Length)//TODO: use a better expression here rahter than based on length { case 1: return (item => item.LinkUrl != null && item.LinkUrl.ToLower().Contains(value)); case 2: { string v1=arr[0].Replace(separator,""); string v2=arr[1].Replace(separator,""); return (item => item.LinkUrl != null && item.LinkUrl.ToLower().Contains(v1) && item.LinkUrl.ToLower().Contains(v2) ); } default: { string v1=arr[0].Replace(separator,""); string v2=arr[1].Replace(separator,""); string v3=arr[2].Replace(separator,""); return (item => item.LinkUrl != null && item.LinkUrl.ToLower().Contains(v1) && item.LinkUrl.ToLower().Contains(v2) && item.LinkUrl.ToLower().Contains(v3) ); } } };
- Resource.tt – genereaza codul pentru fisiere resx – de exemplu , pentru fiecare clasa am nevoie de “edit”, “delete”, “new” , nume de label pentru (aproape) fiecare proprietate
<data name="Accessed_AddNew" xml:space="preserve"><value>Add new Accessed</value></data> <data name="Accessed_List" xml:space="preserve"><value>List of Accessed</value></data> <data name="Accessed_Found_Multi" xml:space="preserve"><value>found {0} Accesseds</value></data> <data name="Accessed_Found_One" xml:space="preserve"><value>One Accessed found</value></data> <data name="Accessed_Found_Zero" xml:space="preserve"><value>No Accessed found</value></data> <data name="Accessed_Save" xml:space="preserve"><value>Save Accessed</value></data> <data name="Accessed_Save_Error" xml:space="preserve"><value>Error saving Accessed</value></data> <data name="Accessed_Delete_Error" xml:space="preserve"><value>Error deleting Accessed</value></data>
- Visitor.tt – daca am nevoie sa generez ceva date despre fisiere. Implementeaza Visitor pattern din Design Patterns.
public partial class Link: FastDatabaseQuery.IReceiveVisitor, I_Link
public partial class Link_Visitor: FastDatabaseQuery.IPropertiesVisitor { public string Visited(FastDatabaseQuery.IReceiveVisitor i) {
Bineinteles ca fiecare cod are nevoie de imbunatatiri. De aceea imi trebuie cod intercalat – si, de obicei, profit de partial in (aproape ) toate formele sale:
1. la definitia clasei
De exemplu,clasa Link_OLAP trebuia sa implementeze si interfata I_Link . Asa incit am adaugat un fisier .cs in care am scris
partial class Link_OLAP:I_Link { }
2. la introducerea unor metode pe care pot sau nu sa le definesc in corpul clasei asociate.
De exemplu, pentru definirea Modelului trebuia sa spun ca LinkID nu este autogenerat( o problema stupida a EF, care incearca sa fie destept)
Cind am facut override la OnModelCreating am definit si urmatoarele:
partial void OnBeginModelCreating(DbModelBuilder modelBuilder,ref bool Continue); partial void OnFinishModelCreating (DbModelBuilder modelBuilder); protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Conventions.Remove<IncludeMetadataConvention>(); bool Continue=true; OnBeginModelCreating(modelBuilder, ref Continue); if(!Continue) return; //construct default modelBuilder.Entity<Accessed>().ToTable(HelperTables.NameTable(Tables_PMKBEntities.Accessed)); modelBuilder.Entity<Accessed>().HasKey(item => item.AccessedID); //other code OnFinishModelCreating(modelBuilder); }
Asa ca am adaugat un nou fisier, am pus partial class si am definit OnFinishModelCreating ( Nu era obligatoriu – de ex., nu am definit OnBeginModelCreating)
public partial class PMKBEntities { partial void OnFinishModelCreating(System.Data.Entity.DbModelBuilder modelBuilder) { modelBuilder.Entity<Link_OLAP>().Property(item => item.LinkID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); } }
Deci, daca nu stiti ce sunt fisierele .tt, atunci cititi de la Hanselmann
si mergeti mai departe.
Puteti downloada solutia de la http://pmkb.codeplex.com/
Rezumat
Fisierele .tt va fac viata mai usoara, daca aveti definita o structura de baza. Folositi-le!
Interesant – intr-adevar, pentru cine e obisnuit cu o abordare database-first, si domain model-ul nu difera mult de structura tabelelor, generarea de cod e cea mai buna varianta.
Important e ca in sfarsit, dupa atatia ani, Microsoft are in sfarsit o solutie adevarata de definite de template-uri/generare de cod, si nu mai trebuie musai sa folosim diverse solutii third-party (CodeSmith, MyGeneration, LLBLGenPro etc.), care chiar daca sunt mai bune uneori, trebuie invatate de cei ce nu le cunosc la fiecare nou proiect..
Multumesc pentru comentariu.
Dar , daca DomainModel difera de structura DB, e o simpla problema de AutoMapper.
Iar .tt – exista de la VS2008 – dupa cite stiu.
Pai AutoMapper nu prea te ajuta in sensul asta – cand domain model-ul difera de modelul relational, partea de mapare ce tine de O/RM cam trebuie definita manual oricum (fie ca e cu atribute, XML sau fluent configuration)..
Automapper e util mai degraba cand ViewModel-ul (sau presentation model-ul sau DTO-urile) difera (putin) de domain model.