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

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

Continuare la Partea a 2-a: Crearea unui proiect

Când o entitate a modelului nostru are o proprietate de tipul unei alte entităţi a domeniului, adică face referire la o altă entitate, putem clarifica acest concept în fişierul de mapping cu tag-ul <many-to-one/>. Continuând să îmbogăţim modelul început la Partea a 2-a, hotărâm să introducem entitatea comandă, şi de cum o comandă are neapărată nevoie de un client, vom putea vedea cum să explicităm acest concept. In realitate entitatea comandă va fi mult mai complexă, dar pentru moment ne mulţumim să observăm relaţia dintre comandă şi cel care a făcut-o.

Adăugăm un fişier mapping construit astfel:

 

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHFromScratch.Entities" 
   3:                                                       assembly="NHFromScratch.Entities">
   4:   <class name="Order" table="Orders">
   5:     <id name="Id" type="Int32">
   6:       <generator class="native"/>
   7:     </id>
   8:     <property name="OrderCode" type="AnsiString" />
   9:     <property name="OrderDate" type="DateTime"/>
  10:     <property name="DueDate" type="DateTime"/>
  11:     <many-to-one name="Customer" class="Customer" column="CustomerId"/>
  12:     <!-- details comes here -->
  13:   </class>
  14:   
  15: </hibernate-mapping>

După cum vedeţi, am adăugat câteva proprietăţi de tip date, dar noutatea o constituie tag-ul <many-to-one/> care ne spune în această definiţie minimală:

  • Numele pe care îl are proprietatea cu atributul “name”
  • Care este entitatea externă, adică tipul său (primul tabel nu ne mai interesează)
  • Opţional numele coloanei (practic numele coloanei ce va fi o foreign-key în tabelul order VS tabelul customer) cu atributul “column”.

Dacă nu aş explicita column, numele ar fi acelaşi cu al proprietăţii. Pe scurt: column aplică şi la proprietăţile simple, cu aceeaşi semantică.

Bun, acum să ne amintim să punem “embedded resource” la noul fişier hbm, pe care-l vom numi în mod întâmplător Order.hbm.xml, lansăm compilarea pentru a obţine o primă versiune a fişierului .cs order.generated.cs, pe care-l vom adăuga proiectului şi-l vom compila.

clip_image002

După ce am făcut acest lucru putem lansa unit testele, şi recrea baza de date:

clip_image004

Testul „trece pe verde” şi observăm cum tabelul order apare în script DDL, şi cum foreign key care mizează la Customer este introdusă în mod oportun în tabelul order. În plus, cei mai atenţi vor fi observat cum proprietatea code a fost definită ca VARCHAR, în timp ce celelalte coloane şir din tabelul customer sunt însă NVARCHAR, acest lucru este provocat de alegerea de a folosi ca tip pentru şirul OrderCode AnsiString în loc de string. Acest lucru nu are nici un efect în codul C#, dar schimbă DDL-ul din database, deci alegeţi-l pe cel mai potrivit. Un alt lucru important, FK-ul poate fi anulat, asta nu e prea bine pentru că am spus că un Order trebuie să aibă obligatoriu un Customer asociat: este de ajuns să folosim atributul not-null = “true” adăugat la tag-ul <many-to-one/>.

 

   1: <many-to-one name="Customer" class="Customer" column="CustomerId" not-null="true"/>

Asta va modifica DDL-ul ca mai jos :

 

   1: create table Orders (
   2:        Id INT IDENTITY NOT NULL,
   3:       OrderCode VARCHAR(255) null,
   4:       OrderDate DATETIME null,
   5:       DueDate DATETIME null,
   6:       CustomerId INT not null,
   7:       primary key (Id)
   8:    )

 

În cele din urmă baza noastră de date va avea această schemă:

clip_image002[5]

Acum putem crea câteva teste, pentru a vedea cum funcţionează modelul. Să spunem că vrem să creăm nişte teste repetitive, deci TestFixture va recrea de fiecare dată o bază de date de la zero, astfel fiecare test va fi independent. Primul test interesant ar putea să fie crearea unei comenzi, cu un nou client simultan. De această dată vom vedea mai bine în detaliu variile operaţii pe care le vom face. Iată codul de test pe care vom încerca să-l facem:

   1: public void CreateAnOrderWithANewCustomer()
   2: {
   3:     using(ISession session = sessionFactory.OpenSession() )
   4:     using (ITransaction transaction = session.BeginTransaction())
   5:     {
   6:         Order order = new Order();
   7:         order.Customer = new Customer()
   8:         {
   9:             Active = true
  10:             ,
  11:             AddressLine1 = "xxxxxxx xxx xxxx"
  12:             ,
  13:             AddressLine2 = "xx xxxxxxx xxxxx"
  14:             ,
  15:             City = "Milano"
  16:             ,
  17:             Name = "Customer Name"
  18:             ,
  19:             ZipCode = "00000"
  20:         };
  21:         order.OrderCode="ORD00000";
  22:         order.OrderDate = DateTime.Now;
  23:         order.DueDate = order.OrderDate.Value.AddMonths(2);
  24:         session.SaveOrUpdate(order);
  25:         transaction.Commit();
  26:     }
  27: }

Mai întâi creăm o „Sesiune”, ceva ce se aseamănă unei conexii la baza de date, dar e mai mult de atât, este o unitate de lucru. În interiorul unei unităţi de lucru, entităţile vor deveni demne de acest nume, iar NHibernate urmăreşte modificările şi se asigură să garanteze unicitatea cu acelaşi id deţinut în baza de date, şi multe alte lucruri. Să spunem câteva regului bune despre session:

  • E uşor să creezi o sesiune (nu putem spune asta despre Session Factory )
  • Încercaţi să faceţi sesiunile scurte din punct de vedere temporal
  • Limitaţi numărul entităţilor cu care lucrează o sesiune
  • Sesiunea nu este multithread.

Imediat după, am deschis o tranzacţie, pentru că la sfârşit vom menţine ceva nou în database, şi vrem să facem toate operaţiile în unitatea noastră de lucru (UOW = unit of work) în mod consistent şi excepţional. Într-un singur UOW putem avea mai multe tranzacţii.

Lansăm testul şi vedem ce se întâmplă în text output al NUnit, pentru a vedea ce face NH în baza de date. Din nefericire testul „trece pe roşu”:

clip_image002[7]

Aceasta este faimoasa eroare:

“NHFromScratch.Tests.TestOrders.CreateAnOrderWithANewCustomer:
NHibernate.PropertyValueException : not-null property references a null or transient valueNHFromScratch.Entities.Order.Customer”

Practic, asta înseamnă că încercăm să salvăm o entitate (Order) care are o referire la altă entitate, dar aceasta din urmă nu este persistentă ( adică transitoria=Transient ). Pentru a rezolva această problemă avem la dispoziţie două căi:

Să salvăm entitatea referită (dacă aceasta mai este tranzitorie) înainte de a salva entitatea comandă:

   1: session.SaveOrUpdate(order.Customer);
   2: session.SaveOrUpdate(order);
   3: transaction.Commit();

Acest lucru face testul să devină „verde”, iar acestea sunt query-urile trimise la baza de date:

 

   1: ***** NHFromScratch.Tests.TestOrders.CreateAnOrderWithANewCustomer
   2: NHibernate: INSERT INTO Customers (Name, AddressLine1, AddressLine2, City, ZipCode, Active) VALUES (@p0, @p1, @p2, @p3, @p4, @p5); select SCOPE_IDENTITY();@p0 = 'Customer Name', @p1 = 'xxxxxxx xxx xxxx', @p2 = 'xx xxxxxxx xxxxx', @p3 = 'Milano', @p4 = '00000', @p5 = True
   3: NHibernate: INSERT INTO Orders (OrderCode, OrderDate, DueDate, CustomerId) VALUES (@p0, @p1, @p2, @p3); select SCOPE_IDENTITY();@p0 = 'ORD00000', @p1 = 11/09/2010 20.48.24, @p2 = 11/11/2010 20.48.24, @p3 = 1

Cealaltă modalitate este aceea de a acţiona asupra atributului „cascade” al tag-ului <many-to-one/>, de exemplu modificându-l astfel:

   1: <many-to-one name="Customer" class="Customer" column="CustomerId" not-null="true" cascade="save-update"/>

 

În test ne putem din nou limita la a salva Order, la toate celelalte consideraţii pentru a menţine în mod consistent operaţiunea asupra database-ului se gândeşte NH, mulţumită atributului „cascate”.

Acum trecem la partea de citire, creăm un test cu câţiva clienţi şi câteva comenzi şi încercăm să facem nişte extrageri de entităţi deja salvate. Să presupunem că creăm o bază de entităţi, într-un mod cu totul analog aceluia de mai sus, creând doi clienţi, Customer A şi Customer B, cu respectiv 2 şi 1 comandă fiecare. Primul test prevede recuperarea entităţilor salvate prin crearea unei interfețe ICriteria. Iată codul de test:

   1: [Test]
   2: public void QueryWithICRiteria()
   3: {
   4:    CreateSomeOrdersAndCustomers();
   5:    using (ISession session = sessionFactory.OpenSession())
   6:    {
   7:        var criteria = session.CreateCriteria<Order>();
   8:        foreach (Order order in criteria.List<Order>())
   9:        {
  10:            Console.WriteLine("Order:{0} Customer:{1}", order.OrderCode, order.Customer.Name);
  11:        }
  12:    }
  13: }

După cum vedeţi există un foreach care imprimă detaliile comenzii şi a clientului asociat acestuia. Dar dacă observăm query-urile pe care le face NH descoperim o problemă:

 

 

clip_image002[9]

 

Practic Lista forţează execuţia primului query, dar de fiecare dată când mergem să căutăm proprietatea „Customer” iată că baza de date primeşte un nou query, pentru a recupera entitatea referită. În ciuda faptului că NH este atât de deştept încât nu efectuează un query pentru acelaşi Customer de două ori, ceea ce se întâmplă nu reprezintă oricum un comportament ideal. Acest lucru se întâmplă pentru că asociaţia many-to-one este din default “lazy”: Asta poate fi util în multe cazuri, dar dacă, ca în cazul nostru ştim anticipat că vrem să avem toate asociaţiile, cădem în anti patter Select N+1. Din fericire putem evita asta, acţionând asupra proprietăţilor interfeţei ICriteria:

 

   1: var criteria = session.CreateCriteria<Order>()
   2: .SetFetchMode("Customer",FetchMode.Eager)
   3: ;

Dacă schimbăm modul fetch al asociativei schimbăm în mod dramatic query-urile executate, ce se reduc la una singură:

   1: SELECT this_.Id as Id3_1_, ... FROM Orders this_ inner join Customers customer2_ on this_.CustomerId=customer2_.Id

 

 

Aceasta este o optimizare pe care trebuie să încercăm mereu să o facem. Puteam obţine acelaşi rezultat doar adăugând atributul “fetch” în tag-ul <many-to-one/>:

   1: <many-to-one name="Customer" class="Customer" column="CustomerId" fetch="join" not-null="true" cascade="save-update"/>

Desigur, într-un scenariu în care am vrea să schimbăm fetch-mode –ul invers, am putea seta prin cod, SetFetchMode cu valoarea select.

Dar să lăsăm Fetch-Mode-ul cu valoarea join şi să încercăm să facem acelaşi query cu HQL:

   1: [Test]
   2: public void QueryWithHql()
   3: {
   4:     CreateSomeOrdersAndCustomers();
   5:     using (ISession session = sessionFactory.OpenSession())
   6:     {
   7:         var query = session.CreateQuery("from Order");
   8:         foreach (Order order in query.List<Order>())
   9:         {
  10:             Console.WriteLine("Order:{0} Customer:{1}", order.OrderCode, order.Customer.Name);
  11:         }
  12:     }
  13: }

Vom descoperi că HQL nu cunoaşte tipul de fetch specificat de atributul fetch al relaţiei <many-to-one/> !

Asta pentru că HQL prevede posibilitatea de a specifica fetch mode –ul textual:

 

   1: var query = session.CreateQuery("from Order o join fetch o.Customer");

 

Această modificare ne readuce la singurul query pentru toate cele trei situaţii.

Şi cu asta am terminat definiţia relaţiei <many-to-one/> şi, având în vedere că acesta este un curs de la *zero* nu am luat unul câte unul fiecare atribut al relaţiei many-to-one, ci o cantitate suficientă pentru a începe folosirea acesteia în mod eficace.

 

Descarca Sursa
Partea a 2-a

Parte 4
Saturday, September 25, 2010 11:30:00 AM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
hbm2net | NHibernate | NHibernate Tutorial

All comments require the approval of the site owner before being displayed.
OpenID
Please login with either your OpenID above, or your details below.
Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, b, strike, strong) where the @ means "attribute." For example, you can use <a href="" title=""> or <blockquote cite="Scott">.  

Enter the code shown (prevents robots):

Live Comment Preview
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