The official Fatica Labs Blog! RSS 2.0
# Saturday, 25 September 2010

Note: For visitors of your site, this entry is only displayed for users with the preselected language Romanian/română (ro)

Continuare la Prima parte: De ce avem nevoie?

În această parte construim un proiect cu NHibernate ce ne va permite menţinerea unei prime entităţi. De fapt partea OR/M va fi menţinută cât mai simplu posibil, nici un fel de reference sau colecţii sau componente: obiectivul este de a crea un schelet ce funcţionează şi ce poate fi testat. Desigur, toate acestea vor servi drept punct de plecare, anumite lucruri vă vor părea evidente, altele mai puţin, procedaţi cum credeţi mai bine, dar cum acesta este un curs „de la zero” vreau să mă opresc asupra tuturor punctelor.

Decidem să punem entităţile într-o Class Library ( dll ). Aceasta este o alegere ce probabil este bine să facem şi pentru a trimite o adevărată soluţie în producţie. Să creăm deci o nouă solution cu Visual Studio. Deosebim numele soluţiei cu NHFromScratch.All, şi adăugăm în interior un nou proiect de tipul class library, pe care-l vom numi NHFromScratch.Entities.

clip_image002

În interiorul directorului rădăcină al soluţiei, creăm un subdirector „Lib”, în stare să conţină dependinţele soluţiei (NHibernate), de exemplu în completarea dependinţei NH vedeţi nunit.framework.dll, dependinţă de care vom avea nevoie în scurt timp când vom vrea să testăm entităţile noastre.

clip_image004

Bun, pasul următor este crearea mapping-ului pentru prima şi singura entitate pe care vrem sa o administrăm. Există nenumărate strategii pentru a crea un mapping pentru NH, cea pe care o prezint aici fiind o soluţie “plain vanilla”, fără înflorituri şi cu un cost zero: cu practica, fiecare alege metoda pe care o consideră mai bună, dar pentru a învăţa eu cred că este de ajutor scrierea fişierului XML. Adăugăm deci un subdirector „Mapping” în class library pe care tocmai am creat-o, iar în acest folder creăm un nou fişier cu numele entităţii (Customer în cazul nostru) cu extensia .hbm.xml. Dacă am abilitat intellisense-ul, după cum am sfătuit în prima parte, unicul tag pe care trebuie să-l reţinem este <hibernate-mapping>, toate celelalte vor fi expuse via intellisense după ce namespace-ul va fi specificat în mod corect, după cum puteţi vedea mai jos:

clip_image006

Este foarte important să vă amintiţi să setaţi “Embedded Resource” ca build action în fişierul de mapping, altfel… configurarea NHibernate-ului nu va funcţiona. Ca să fim precişi acest lucru s-ar putea evita, dar pentru asta ar trebui să distribuim fişierele de mapping împreună cu aplicaţia, şi sincer să fiu, este o opţiune care nu îmi place şi pe care nu am mai văzut-o pusă în practică.

După care nu rămâne decât să scriem efectiv mapping-ul. În acest prim test vom folosi doar trei elemente de mapping: class,key si property, practic minimul indispensabil şi pentru a avea o unitate la lucru. Vom aprofunda variile aspecte în următoarele părţi.

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" 
   3: assembly="NHFromScratch.Entities">
   4: <class name="Customer" table="Customers">
   5: <id name="Id" type="Int32">
   6: <generator class="native"/>
   7: </id>
   8: <property name="Name" type="String" not-null="true"/>
   9: <property name="AddressLine1" type="String" not-null="true"/>
  10: <property name="AddressLine2" type="String" />
  11: <property name="City" type="String" not-null="true"/>
  12: <property name="ZipCode" type="String" not-null="true"/>
  13: <property name="Active" type="Boolean" not-null="true"/>
  14: </class>
  15: </hibernate-mapping>

Să spunem oricum câteva cuvinte pentru a explica în mare ce conţine mapping-ul entităţii Customer. Mai întâi de toate sunt specificate namespace-ul şi assembly-ul conţinute în entitatea mappată. Acest lucru nu este obligatoriu, dar ne permite să scriem într-un mod mai clar numele clasei mai departe. Tag class-a indică deci clasa pe care intenţionăm să o utilizăm ca entity. Amintim că adesea nu există un raport 1:1 între tabele şi entităţi, dar în acest exemplu simplu vom avea o astfel de situaţie. Tag class-a ne permite să definim numele clasei şi tabelul cu care se asociază în BD. Tag-ul din josul <id>- ului este necesar: NHibernate vrea să ştie în ce fel database-ul „distinge” entităţile. Există nenumărate moduri pentru a face acest lucru, iar modul este ales de sub-tag generator: în acest exemplu am specificat un generator „nativ”, ce înseamnă că un id este univoc desemnat de BD (identity, auto-crescător) oricare ar fi numele său în baza de date target. Succesiv vin proprietăţile, cu numele lor frumoase şi tipuri dat. Veţi observa că nu am specificat nici un nume pentru relativa coloană în BD: simplu, dacă numele este acelaşi, nimeni nu ne obligă să o facem, să nu ne repetăm. Dimpotrivă, tipul ar fi un surplus dacă am scrie manual clasa de mai jos: NHibernate are obiceiul de a deduce informaţiile oriunde este posibil. Dar noi nu vrem să creăm clasa manual, şi de aceea ne folosim de hbm2net (vezi prima parte). Pentru a dezlănţui acest tool, folosim un pre build step al Visual Studio, ca cel de mai jos:

clip_image008

Practic îi cerem hbm2net-ului să lucreze pe toate fişierele .hbm.xml pe care le găseşte în subdirectorul mapping al soluţiei, să pună output-urile (mai bine spus clasele) în directorul proiectului. Deşi hbm2net este un editor template based care poate genera aproape orice artefact plecând de la hbm, fără parametrii se limitează să genereze clasele ce corespund mapping-ului. Lansând build-ul în output ar trebui să vedeţi şi progress-ul hbm2net-ului:

clip_image010

Odată terminat build-ul, veţi avea fişierul autogenerat pentru entitate în file system. Adăugaţi-l la proiect.

clip_image012

Aruncaţi o privire la clasa auto generată, aruncaţi o privire şi la mapping: dacă aţi încercat vreun alt tool pentru NHibernate veţi observa că lucrul pe care l-aţi făcut până acum pare mult mai simplificat. Scriem numai strictul necesar, ceea ce nu ne trebuie lăsăm de-o parte, pentru că acesta este NHibernate de la ZERO.

clip_image014

Pentru a completa soluţia adăugăm o reference la NHibernate: NHibernate si Iesi.Collection sunt singurele assembly pe care trebuie să le anexăm la momentul build-ului.

A sosit în sfârşit momentul să facem testul. Adăugăm la solution un proiect de tipul Class Library, unde vom introduce unit testele .

clip_image016

Soluţia testului, asemănător unui proiect care trebuie să meargă în producţie, are nevoie de anumite reference non statice de la NHibernate. Noi le avem în directorul Lib al soluţiei, trebuie să le mutăm lângă cea a dll-ului de producţie. Putem alege să facem acest lucru cu un post-build step, după cum este ilustrat mai jos.

clip_image018

Şi reference-urile acestui proiectului cer puţin mai multă implicare: avem nevoie de Iesi.Collection & NHibernate ca de obicei dar şi de assembly –ul nostru cu entităţi, de log4net pentru logging, şi în plus, desigur, de nunit.framework.

clip_image020

Mie personal îmi place bara verde a NUnit- ului, deci testăm proiectul cu NUnit, punând NUnit.exe ca “start external program”.

clip_image022

Acum creăm unit testul nostru, cu scopul de a configura NH, puţin log, şi vedem dacă reuşim să creăm Database-ul. Da, în acest tutorial database-ul nu îl vom face manual, pentru că, chiar dacă acesta este NHibernate de la ZERO, vrem să scriem cât mai puţin cod posibil.

Codul unit testului este următorul:

 

 

   1: namespace NHFromScratch.Tests
   2: {
   3:     [TestFixture]
   4:     public class TestCustomer
   5:     {
   6:         static TestCustomer()
   7:         {
   8:             ConfigureLogForNet();
   9:         }
  10:  
  11:         ISessionFactory sessionFactory;
  12:         [SetUp]
  13:         public void Setup()
  14:         {
  15:             
  16:             CreateSessionFactory();
  17:         }
  18:  
  19:         [Test]
  20:         public void CanCreatedatabse()
  21:         {
  22:             SchemaExport export = new SchemaExport(CreateConfiguration());
  23:             export.Execute(true, true, false);
  24:         }
  25:  
  26:         [Test]
  27:         public void CanPersistCustomer()
  28:         {
  29:             Console.WriteLine("\n****** SAVE A CUSTOMER ********");
  30:             //save a customer
  31:             using (var session = sessionFactory.OpenSession())
  32:             using(var transaction = session.BeginTransaction())
  33:             {
  34:  
  35:                 Customer c = new Customer()
  36:                 {
  37:                     Active = true
  38:                      ,
  39:                     AddressLine1 = "xxxx"
  40:                      ,
  41:                     City = "Cuneo"
  42:                      ,
  43:                     Name = "Bill Gates"
  44:                      ,
  45:                     ZipCode = "12060"
  46:                 };
  47:                 session.Save(c);
  48:                 transaction.Commit();
  49:             }
  50:             Console.WriteLine("\n****** RETRIEVE A CUSTOMER ********");
  51:             //retrieve a customer
  52:             using (var session = sessionFactory.OpenSession())
  53:             
  54:             {
  55:  
  56:                 var customer = session.CreateCriteria<Customer>()
  57:                     .Add(Expression.Eq("Name", "Bill Gates"))
  58:                     .UniqueResult();
  59:  
  60:                 Assert.NotNull(customer);
  61:             }
  62:  
  63:             Console.WriteLine("\n****** MODIFY A CUSTOMER ********");
  64:             //modify a customer
  65:             using (var session = sessionFactory.OpenSession())
  66:             using (var transaction = session.BeginTransaction())
  67:             {
  68:  
  69:                 var customer = session.CreateCriteria<Customer>()
  70:                     .Add(Expression.Eq("Name", "Bill Gates"))
  71:                     .UniqueResult<Customer>();
  72:                 customer.ZipCode = "0000";
  73:                 Assert.NotNull(customer);
  74:                 
  75:                 transaction.Commit();
  76:             }
  77:  
  78:  
  79:             Console.WriteLine("\n****** VERIFY CUSTOMER IS MODIFIED ********");
  80:             //verify mod
  81:             using (var session = sessionFactory.OpenSession())
  82:             {
  83:  
  84:                 var customer = session.CreateCriteria<Customer>()
  85:                     .Add(Expression.Eq("Name", "Bill Gates"))
  86:                     .UniqueResult<Customer>();
  87:                 
  88:                 Assert.NotNull(customer);
  89:                 Assert.AreEqual("0000", customer.ZipCode);
  90:             }
  91:  
  92:  
  93:             Console.WriteLine("\n****** DELETE A CUSTOMER ********");
  94:             //delete a customer
  95:             using (var session = sessionFactory.OpenSession())
  96:             using (var transaction = session.BeginTransaction())
  97:             {
  98:  
  99:                 var customer = session.CreateCriteria<Customer>()
 100:                     .Add(Expression.Eq("Name", "Bill Gates"))
 101:                     .UniqueResult();
 102:                 session.Delete(customer);
 103:                 transaction.Commit();
 104:             }
 105:             Console.WriteLine("\n****** VERIFY CUSTOMER IS DELETED ********");
 106:             //verify delete
 107:             using (var session = sessionFactory.OpenSession())
 108:             
 109:             {
 110:  
 111:                 var customer = session.CreateCriteria<Customer>()
 112:                     .Add(Expression.Eq("Name", "Bill Gates"))
 113:                     .UniqueResult();
 114:                 Assert.IsNull(customer);
 115:             }
 116:         }
 117:  
 118:  
 119:  
 120:         private static void ConfigureLogForNet()
 121:         {
 122:             TraceAppender app = new TraceAppender();
 123:             app.Layout = new SimpleLayout();
 124:             //BasicConfigurator.Configure( app);
 125:         }
 126:  
 127:         private void CreateSessionFactory()
 128:         {
 129:             Configuration cfg = CreateConfiguration();
 130:             sessionFactory = cfg.BuildSessionFactory();
 131:         }
 132:  
 133:         private static Configuration CreateConfiguration()
 134:         {
 135:             Configuration cfg = new Configuration();
 136:             cfg.Configure();
 137:             // implicitamente carichiamo tutti i mapping che si trovano nell'assembly che
 138:             // contiene customer
 139:             cfg.AddAssembly(typeof(Customer).Assembly);
 140:             return cfg;
 141:         }
 142:  
 143:  
 144:     }
 145: }

 

 

Odată ce am făcut asta lansăm testul “CanCreateDatabase” şi, după cum probabil suspectăm, ajungem pe roşu cu logger-ul care spune ceva de genul:

 

  

 

 

clip_image024

În realitate am „uitat” să configurăm NHibernat –ul, şi deci la care BD poate miza? După cum vedeţi, faptul că avem log-ul abilitat reduc dubiile în privinţa greşelii. Printre altele lipseşte chiar şi database-ul…

Deci creăm mai întâi un database gol, nu este nevoie de nici un tabel, lăsăm ca NHibernate să facă munca grea.

clip_image026

În exemplu am creat un database cu numele NHFROMSCRATCH în SQLEXPRESS local. Apoi, întorcându-mă la , când se vedea conţinutul ZIP, vă veţi aminti de directorul Configuration_Templates : în aceast director există nişte template –uri de configurare, unde este de ajuns să înlocuieşti câteva lucruri pentru a fi pregătiţi să lucrăm cu baza de date aleasă:

clip_image028

În cazul nostru alegem MSSQL. Conţinutul fişierului după modificările de caz este acesta:

 

   1: <?xml version="1.0" encoding="utf-8" ?>
   2:  
   3:  
   4: <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
   5:   <session-factory name="NHibernate.Test">
   6:     <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
   7:     <property name="connection.connection_string">Server=.\SQLEXPRESS;initial catalog=NHFROMSCRATCH;Integrated Security=SSPI</property>
   8:     <property name="adonet.batch_size">10</property>
   9:     <property name="show_sql">true</property>
  10:     <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
  11:     <property name="use_outer_join">true</property>
  12:     <property name="command_timeout">60</property>
  13:     <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
  14:     <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
  15:   </session-factory>
  16: </hibernate-configuration>

 

După cum se vede, modificările substanţiale se afla în proprietatea connection string, şi în show sql, pe care l-am pus true, pentru a vizualiza query-urile în timp ce NHibernate le execută: un truc ce ne va permite nu numai să vedem cum NHibernate face query-urile, dar mai ales câte face, pentru a face performance tuning. Această sesiune de configurare va putea fi pusă în file-ul de configurare a aplicaţiei, sau aproape de fişierul binarului ce o foloseşte într-un fişier numit hibernate.cfg.xml, soluţie aleasă în exemplu.

clip_image030

Să ne amintim că, după ce am introdus fişierul Hibernate.cfg.xml în proiect, trebuie să setăm flag-ul “copy if newer” astfel fiind copiat automat în directorul de ieşire.

Să vedem acum cum este făcut testul “CanCreateDatabase”:

 

   1: [Test]
   2: public void CanCreatedatabse()
   3: {
   4:    SchemaExport export = new SchemaExport(CreateConfiguration());
   5:    export.Execute(true, true, false);
   6: }

 

 

 

Folosim clasa Helper SchemaExport: această clasă, pornind de la configurarea curentă, se ocupă cu crearea schemei database-ului. Acum lansând testul, acesta se comportă mai bine, iar în partea console putem vedea DLL-ul care a fost trimis la database.

clip_image032

Prin flag-ul funcţiei Export, putem supune script-ul la sistemul database de mai jos, în cazul nostru vom obţine în BD singurul tabel al minusculului nostru model de test:

clip_image034

Deci să facem o scurtă recapitulare până la acest punct: cu numai un fişier (cel de mapping) am creat un database şi codul c#. Şi totul graţie hbm2net-ului, ce este un codegenerator open source ce se poate recupera de la NHContrib. Cu puţină răbdare putem modifica şi/sau scrie template adiţionali pentru hbm2net pentru a genera alte tipuri de artefacts, de exemplu DTO sau altele, dar asta vom vedea mai târziu. Practic putem vedea hbm2net ca un generator de cod programabil, care din default produce codul entităţilor pornind de la fişierul hbm.

clip_image036

Acum putem scrie codul de probă pentru model. Dând valoare show-sql –ului din configurare „true”, NH trimite în standard output query-urile, deci la momentul debug-ului este posibil să inspecţionăm ce se întâmplă după culise. Simplul nostru unit test creează/modifică/anulează o simplă instanţă a entităţii customer; iată codul unit testului care face toate acestea :

   1: namespace NHFromScratch.Tests
   2: {
   3:     [TestFixture]
   4:     public class TestCustomer
   5:     {
   6:         static TestCustomer()
   7:         {
   8:             ConfigureLogForNet();
   9:         }
  10:  
  11:         ISessionFactory sessionFactory;
  12:         [SetUp]
  13:         public void Setup()
  14:         {
  15:             
  16:             CreateSessionFactory();
  17:         }
  18:  
  19:         [Test]
  20:         public void CanCreatedatabse()
  21:         {
  22:             SchemaExport export = new SchemaExport(CreateConfiguration());
  23:             export.Execute(true, true, false);
  24:         }
  25:  
  26:         [Test]
  27:         public void CanPersistCustomer()
  28:         {
  29:             Console.WriteLine("\n****** SAVE A CUSTOMER ********");
  30:             //save a customer
  31:             using (var session = sessionFactory.OpenSession())
  32:             using(var transaction = session.BeginTransaction())
  33:             {
  34:  
  35:                 Customer c = new Customer()
  36:                 {
  37:                     Active = true
  38:                      ,
  39:                     AddressLine1 = "xxxx"
  40:                      ,
  41:                     City = "Cuneo"
  42:                      ,
  43:                     Name = "Bill Gates"
  44:                      ,
  45:                     ZipCode = "12060"
  46:                 };
  47:                 session.Save(c);
  48:                 transaction.Commit();
  49:             }
  50:             Console.WriteLine("\n****** RETRIEVE A CUSTOMER ********");
  51:             //retrieve a customer
  52:             using (var session = sessionFactory.OpenSession())
  53:             
  54:             {
  55:  
  56:                 var customer = session.CreateCriteria<Customer>()
  57:                     .Add(Expression.Eq("Name", "Bill Gates"))
  58:                     .UniqueResult();
  59:  
  60:                 Assert.NotNull(customer);
  61:             }
  62:  
  63:             Console.WriteLine("\n****** MODIFY A CUSTOMER ********");
  64:             //modify a customer
  65:             using (var session = sessionFactory.OpenSession())
  66:             using (var transaction = session.BeginTransaction())
  67:             {
  68:  
  69:                 var customer = session.CreateCriteria<Customer>()
  70:                     .Add(Expression.Eq("Name", "Bill Gates"))
  71:                     .UniqueResult<Customer>();
  72:                 customer.ZipCode = "0000";
  73:                 Assert.NotNull(customer);
  74:                 
  75:                 transaction.Commit();
  76:             }
  77:  
  78:  
  79:             Console.WriteLine("\n****** VERIFY CUSTOMER IS MODIFIED ********");
  80:             //verify mod
  81:             using (var session = sessionFactory.OpenSession())
  82:             {
  83:  
  84:                 var customer = session.CreateCriteria<Customer>()
  85:                     .Add(Expression.Eq("Name", "Bill Gates"))
  86:                     .UniqueResult<Customer>();
  87:                 
  88:                 Assert.NotNull(customer);
  89:                 Assert.AreEqual("0000", customer.ZipCode);
  90:             }
  91:  
  92:  
  93:             Console.WriteLine("\n****** DELETE A CUSTOMER ********");
  94:             //delete a customer
  95:             using (var session = sessionFactory.OpenSession())
  96:             using (var transaction = session.BeginTransaction())
  97:             {
  98:  
  99:                 var customer = session.CreateCriteria<Customer>()
 100:                     .Add(Expression.Eq("Name", "Bill Gates"))
 101:                     .UniqueResult();
 102:                 session.Delete(customer);
 103:                 transaction.Commit();
 104:             }
 105:             Console.WriteLine("\n****** VERIFY CUSTOMER IS DELETED ********");
 106:             //verify delete
 107:             using (var session = sessionFactory.OpenSession())
 108:             
 109:             {
 110:  
 111:                 var customer = session.CreateCriteria<Customer>()
 112:                     .Add(Expression.Eq("Name", "Bill Gates"))
 113:                     .UniqueResult();
 114:                 Assert.IsNull(customer);
 115:             }
 116:         }
 117:  
 118:  
 119:  
 120:         private static void ConfigureLogForNet()
 121:         {
 122:             TraceAppender app = new TraceAppender();
 123:             app.Layout = new SimpleLayout();
 124:             //BasicConfigurator.Configure( app);
 125:         }
 126:  
 127:         private void CreateSessionFactory()
 128:         {
 129:             Configuration cfg = CreateConfiguration();
 130:             sessionFactory = cfg.BuildSessionFactory();
 131:         }
 132:  
 133:         private static Configuration CreateConfiguration()
 134:         {
 135:             Configuration cfg = new Configuration();
 136:             cfg.Configure();
 137:             // implicitamente carichiamo tutti i mapping che si trovano nell'assembly che
 138:             // contiene customer
 139:             cfg.AddAssembly(typeof(Customer).Assembly);
 140:             return cfg;
 141:         }
 142:  
 143:  
 144:     }
 145: }

 

 

Şi iată un screenshots despre cum NH operează din spatele culiselor:

clip_image038

În următoarea parte vom vedea mai bine detaliile fişierului mapping şi tag-ul <many-to-one> împreună cu <bag>.

Descarca Sursa

Prima parte

Partea a 3-a

Saturday, 25 September 2010 09:04:54 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
hbm2net | NHibernate | NHibernate Tutorial

My Stack Overflow
Contacts

Send mail to the author(s) E-mail

Tags
profile for Felice Pollano at Stack Overflow, Q&A for professional and enthusiast programmers
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2019
Felice Pollano
Sign In
Statistics
Total Posts: 157
This Year: 0
This Month: 0
This Week: 0
Comments: 127
This blog visits
All Content © 2019, Felice Pollano
DasBlog theme 'Business' created by Christoph De Baene (delarou) and modified by Felice Pollano