PMKB 1- importanta generarii automate a codului

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

  1. 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);    
    
  2. 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
        {
    
  3. 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);
    
                }
    
  4. 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) );
            				}
            
            		}
            			
            
            	};	 
    
    
    
  5. 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>
    
    
  6. 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

http://www.hanselman.com/blog/T4TextTemplateTransformationToolkitCodeGenerationBestKeptVisualStudioSecret.aspx

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!

3 thoughts on “PMKB 1- importanta generarii automate a codului

  1. 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..

    1. 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.

      1. 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.

Leave a Reply

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