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)

Ne poate fi de ajutor în multe cazuri să avem un server web *fără* să instalăm IIS şi fără să fie necesar să facem un hosting pentru ASP.NET, dar având un „plain vanilla” server http, care să răspundă direct la cererile socket, care să fie uşor de extins cu module custom, simplu şi de încredere. În codeplex se găseşte C# WebServer , deploy-ul în situaţia minimală constă într-un singur DLL (ce frumos!), iar pentru a-l pune în funcţiune sunt de ajuns câteva rânduri de cod:

 

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         // create a server.
   6:         var server = new Server();
   7:  
   8:         // same as previous example.
   9:         server.Resources.Add(new FileResources("/", Environment.CurrentDirectory + "\\files\\"));
  10:         server.Add(new FileModule(server.Resources, false));
  11:  
  12:         // use one http listener.
  13:         server.Add(HttpListener.Create(IPAddress.Any, 8085));
  14:  
  15:         // add our own module.
  16:         server.Add(new Streamer());
  17:  
  18:  
  19:         // start server, can have max 5 pending accepts.
  20:         server.Start(5);
  21:  
  22:         Console.ReadLine();
  23:     }
  24: }

Faptul că avem exigenţe reduse de dependinţe, face din acesta un instrument ideal pentru îmbogăţirea cu UI a aplicaţiilor „service” pe care le vom scrie.

Saturday, 25 September 2010 14:58:35 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
Programmin

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 3-a : <many-to-one/>

Continuând să lucrăm asupra comenzilor minimale pe care le analizăm acum, ne-ar putea fi folositor să ştim care comenzi sunt asociate unui anumit Customer. Lăsând modelul aşa cum e, o putem face, atât prin HQL cât şi prin ICriteria. Amintesc că acesta este cursul NHibernate de la *ZERO*, deci nu folosim LINQ to NH, şi nici un alt tip de API extern NHibernate –ului, nu pentru că acestea sunt sau nu eficace, ci doar pentru că vrem să analizăm comportamentul de bază. Pur şi simplu creăm cele două unit-teste, primul cu HQL:

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

Care lansat produce următorul output la console:

 

   1: NHibernate: select order0_.Id as Id3_0_, customer1_.Id as Id2_1_, order0_.OrderCode as OrderCode3_0_, order0_.OrderDate as OrderDate3_0_, order0_.DueDate as DueDate3_0_, order0_.CustomerId as CustomerId3_0_, customer1_.Name as Name2_1_, customer1_.AddressLine1 as AddressL3_2_1_, customer1_.AddressLine2 as AddressL4_2_1_, customer1_.City as City2_1_, customer1_.ZipCode as ZipCode2_1_, customer1_.Active as Active2_1_ from Orders order0_ inner join Customers customer1_ on order0_.CustomerId=customer1_.Id, Customers customer2_ where order0_.CustomerId=customer2_.Id and customer2_.Name=@p0;@p0 = 'Customer A'
   2: Order:ORD00000 Customer:Customer A
   3: Order:ORD00001 Customer:Customer A

Observăm că în HQL am specificat oricum join fetch-ul în asociaţia o.Customer:asta tot pentru a evita antipattern-ul select N+1.

Acelaşi rezultat îl putem obţine cu API ICriteria, iată unit-testul:

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

Acesta produce următorul output de trace:

 

   1: NHibernate: SELECT this_.Id as Id9_1_, this_.OrderCode as OrderCode9_1_, this_.OrderDate as OrderDate9_1_, this_.DueDate as DueDate9_1_, this_.CustomerId as CustomerId9_1_, customer1_.Id as Id8_0_, customer1_.Name as Name8_0_, customer1_.AddressLine1 as AddressL3_8_0_, customer1_.AddressLine2 as AddressL4_8_0_, customer1_.City as City8_0_, customer1_.ZipCode as ZipCode8_0_, customer1_.Active as Active8_0_ FROM Orders this_ inner join Customers customer1_ on this_.CustomerId=customer1_.Id WHERE customer1_.Name = @p0;@p0 = 'Customer A'
   2: Order:ORD00000 Customer:Customer A
   3: Order:ORD00001 Customer:Customer A

Deci am putea spune că problema este rezolvată… dar din nefericire nu într-un mod object oriented. Ar fi mai bine să avem o colecţie de comenzi la care obiectul Customer poate accesa cu uşurinţă, ceva de genul customer.Orders. Pentru a face acest lucru ne vine în ajutor cel mai simplu tag de asociaţie pentru a exprima o colecţie de entităţi legate între ele:

<bag/>

Asta ne redă din punct de vedere object oriented ceva ce există deja în baza de date în formă implicită. Dacă în algebra relaţională o FK exprimă de la sine o relaţie bidirecţională, când vorbim despre obiecte această relaţie trebuie explicitată. Deci este necesar să modificăm mapping-ul prin simpla adăgare în clasa Customer al unui <bag/> al Comenzii:

 

 

   1: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHFromScratch.Entities" 
   2:                                                       assembly="NHFromScratch.Entities">
   3:   <class name="Customer" table="Customers">
   4:     <id name="Id" type="Int32">
   5:       <generator class="native"/>
   6:     </id>
   7:     <property name="Name" type="String" not-null="true"/>
   8:     <property name="AddressLine1" type="String" not-null="true"/>
   9:     <property name="AddressLine2" type="String" />
  10:     <property name="City" type="String" not-null="true"/>
  11:     <property name="ZipCode" type="String" not-null="true"/>
  12:     <property name="Active" type="Boolean" not-null="true"/>
  13:     <bag name="Orders">
  14:       <key column="CustomerId"/>
  15:       <one-to-many class="Order"/>
  16:     </bag> 
  17:   </class>
  18:   
  19: </hibernate-mapping>

 

Făcând un rebuild al soluţiei, obţinem o diagramă de clase pentru entităţile noastre:

 

clip_image002

Care ne arată de fapt relaţia bidirecţională dintre client şi comenzile sale. Să vedem acum cu un alt unit test cum putem lucra cu un Customer şi obţine comenzile ce aparţin acestuia:

 

   1: [Test]
   2: public void QueryCustomerOrdersWithBag()
   3: {
   4:     CreateSomeOrdersAndCustomers();
   5:     using (ISession session = sessionFactory.OpenSession())
   6:     {
   7:         var customerA = session.CreateCriteria<Customer>()
   8:             .Add(Expression.Eq("Name", "Customer A")
   9:             ).UniqueResult<Customer>();
  10:         Console.WriteLine("Obtained customer instance");
  11:         foreach (var order in customerA.Orders)
  12:         {
  13:             Console.WriteLine("Order:{0} Customer:{1}", order.OrderCode, order.Customer.Name);
  14:         }
  15:     }
  16: }

Aceasta produce următorul output la console:

 

   1: Obtained customer instance
   2: NHibernate: SELECT orders0_.CustomerId as CustomerId1_, orders0_.Id as Id1_, orders0_.Id as Id3_0_, orders0_.OrderCode as OrderCode3_0_, orders0_.OrderDate as OrderDate3_0_, orders0_.DueDate as DueDate3_0_, orders0_.CustomerId as CustomerId3_0_ FROM Orders orders0_ WHERE orders0_.CustomerId=@p0;@p0 = 1
   3: Order:ORD00000 Customer:Customer A
   4: Order:ORD00001 Customer:Customer A

După cum se observă, colecţia a fost recuperată de la storage doar în momentul necesar. Şi în collection avem un comportament „lazy”, care în multe cazuri este oportun, dar nu întotdeauna. Şi aici putem modifica comportamentul lazy specificându-l în mapping:

 

   1: <bag name="Orders" fetch="join">
   2:   <key column="CustomerId"/>
   3:   <one-to-many class="Order"/>
   4: </bag> 

Recompilând testul de dinainte obţinem un output uşor diferit:

 

   1: Obtained customer instance
   2: Order:ORD00000 Customer:Customer A
   3: Order:ORD00001 Customer:Customer A

 

 

Nici un query: o dată cu încărcarea customer-ului a fost încărcată şi asociaţia cu collection a comenzii, query-ul a fost de fapt următorul:

 

   1: SELECT this_.Id ... orders2_.CustomerId as CustomerId3_... FROM Customers this_ left outer join Orders orders2_ on this_.Id=orders2_.CustomerId WHERE this_.Name = @p0;@p0 = 'Customer A'

 

In toate cazurile, bag-ul care ne soseşte este o listă potenţial dezordonată. Putem cere NH-ului să facă, sau mai bine spus să ceară bazei de date să facă, o ordonare în baza unei anumite proprietăţi a entităţii colecţionate. In exemplul nostru am putea vrea o ordonare în funcţie de data comenzii, adăugând la mapping acest atribut:

 

   1: <bag name="Orders" fetch="join" order-by="OrderDate" >

 

Şi obţinând deci interogaţia următoare:

 

1: SELECTFROM Customers this_ left outer join Orders orders2_ on this_.Id=orders2_.CustomerId WHERE this_.Name = @p0

ORDER BY orders2_.OrderDate;

@p0 = 'Customer A'

 

Cu oportuna clauză id ordonare. Să vedem acum ce se întâmplă făcând nişte teste cu HQL. Primul:

 

   1: [Test]
   2: public void QueryCustomerOrdersWithBagUsingHQL()
   3: {
   4:    CreateSomeOrdersAndCustomers();
   5:    using (ISession session = sessionFactory.OpenSession())
   6:    {
   7:        IQuery query = session.CreateQuery("from Customer c where c.Name=:name")
   8:            .SetParameter("name", "Customer A");
   9:        
  10:        var list = query.List<Customer>();
  11:        Assert.AreEqual(1, list.Count);
  12:        var customerA = list[0];
  13:        foreach (var order in customerA.Orders)
  14:        {
  15:            Console.WriteLine("Order:{0} Customer:{1}", order.OrderCode, order.Customer.Name);
  16:        }
  17:    }
  18: }

 

Observăm că numărul de query revine la un comportament lazy:

 

 

   1: select customer0_.Id as Id2_, customer0_.Name as Name2_, customer0_.AddressLine1 as AddressL3_2_, customer0_.AddressLine2 as AddressL4_2_, customer0_.City as City2_, customer0_.ZipCode as ZipCode2_, customer0_.Active as Active2_ from Customers customer0_ where customer0_.Name=@p0;@p0 = 'Customer A'
   2: SELECT orders0_.CustomerId as CustomerId1_, orders0_.Id as Id1_, orders0_.Id as Id3_0_, orders0_.OrderCode as OrderCode3_0_, orders0_.OrderDate as OrderDate3_0_, orders0_.DueDate as DueDate3_0_, orders0_.CustomerId as CustomerId3_0_ FROM Orders orders0_ WHERE orders0_.CustomerId=@p0 ORDER BY orders0_.OrderDate;@p0 = 1
   3: Order:ORD00000 Customer:Customer A
   4: Order:ORD00001 Customer:Customer A

 

Cu toate acestea, putem specifica şi în HQL politica fetch, modificând HQL-ul in: “from Customer c join fetch c.Orders where c.Name=:name”. Din nefericire testul eşuează în mod jalnic:

 

clip_image002[5]

 

Acesta este un comportament „ciudat” al NH-ului. De fapt există un singur Customer în testele noastre care satisface relaţia cerută, totuşi efectuând join-ul, ne sunt restituite mai multe record-uri (n record-uri, unde n reprezintă numărul comenzilor). Trebuie menţionat că NH nu creează instanţe diferite ale obiectelor, ci pune în lista output-ului nişte references ale aceleiaşi entităţi.

Se poate remedia la această problemă cu un “ResultTransformer”, adică un post-processor al rezultatelor din NH. Putem construi nişte post processori (IResultTransformer ) pentru a manipula în modul pe care-l considerăm oportun rezultatele unei ICriteria sau cele ale unui HQL. NH furnizează anumiţi transformer de utilităţi, unul dintre aceştia ne este util în această situaţie complicată:

 

   1: IQuery query = session.CreateQuery("from Customer c join fetch c.Orders where c.Name=:name")
   2:     .SetParameter("name", "Customer A")
   3:     .SetResultTransformer(Transformers.DistinctRootEntity)
   4:     ;

Această transformare face ca entităţile „identice„ să devină una singură, şi iată că liniile întoarse sunt acelea pe care le aşteptam, iar query-ul devine unul singur, după ce am specificat un comportament non lazy.

Ce se întâmplă dacă încercăm să înlăturăm nişte comenzi de la client? ( atenţie, în realitate acest lucru nu se va întâmpla aproape niciodată: vor exista probabil anumite strategii soft-delete, aici facem doar nişte exemple ) Iată un unit test pentru a testa acest scenariu:

 

   1: [Test]
   2: public void RemoveOrderForCustomers()
   3: {
   4:     CreateSomeOrdersAndCustomers();
   5:     using (ISession session = sessionFactory.OpenSession())
   6:     using(ITransaction trns = session.BeginTransaction() )
   7:     {
   8:         var customerA = session.CreateCriteria<Customer>()
   9:            .Add(Expression.Eq("Name", "Customer A")
  10:            ).UniqueResult<Customer>();
  11:         Console.WriteLine("Obtained customer instance");
  12:         customerA.Orders.RemoveAt(0);
  13:         trns.Commit();
  14:     }
  15: }

Dacă lansăm acest test, obţinem următoarea eroare:

   1: NHFromScratch.Tests.TestOrders.RemoveOrderForCustomers:
   2: NHibernate.Exceptions.GenericADOException : could not delete collection rows: [NHFromScratch.Entities.Customer.Orders#1][SQL: UPDATE Orders SET CustomerId = null WHERE CustomerId = @p0 AND Id = @p1]
   3:   ---->; System.Data.SqlClient.SqlException : Cannot insert the value NULL into column 'CustomerId', table 'NHFROMSCRATCH.dbo.Orders'; column does not allow nulls. UPDATE fails.
   4: The statement has been terminated.

Practic NH încearcă să „desprindă” comanda eliberând-o de Customer, dar noi am specificat faptul că Customer-ul nu nu poate fi null, deci logica e corectă. Singurul mod este să spunem că owner-ul acestei asociaţii este Customer, introducând inverse=”true” cu intenţia de a spune că „partea”asociaţiei nu este responsabilă de colecţie, astfel:

 

 

   1: <bag name="Orders" fetch="join" order-by="OrderDate" inverse="true">
   2:   <key column="CustomerId"/>
   3:   <one-to-many class="Order"/>
   4: </bag> 

Din păcate testul continuă să nu funcţioneze, de această dată ne zice că nimic nu a fost anulat:

clip_image004

Ceea ce putem face este să forţăm anularea cu parametrul „cascade”:

 

   1: <bag name="Orders" fetch="join" order-by="OrderDate" inverse="true" cascade="all-delete-orphan">
   2:   <key column="CustomerId"/>
   3:   <one-to-many class="Order"/>
   4: </bag> 

Şi iată că în sfârşit testul funcţionează:

clip_image006

Parametrul cascade are efecte similare şi pentru operaţiunile de introducere a noilor comenzi: acestea vor fi salvate automat în momentul salvării entităţii Customer.

Înainte de a încheia această parte merită să aruncăm o privire asupra unui comportament pe care este bine să-l cunoaştem date fiind problematicile de optimizare. Să presupunem că vrem să ştim câte comenzi are un Customer, şi că avem asociaţia în modalitatea lazy:

 

   1: [Test]
   2:    public void OrdersCount()
   3:    {
   4:        CreateSomeOrdersAndCustomers();
   5:        using (ISession session = sessionFactory.OpenSession())
   6:        {
   7:            var customerA = session.CreateCriteria<Customer>()
   8:               .Add(Expression.Eq("Name", "Customer A")
   9:               ).UniqueResult<Customer>();
  10:            Console.WriteLine("Obtained customer instance");
  11:            Assert.AreEqual(2, customerA.Orders.Count);
  12:        }
  13:       
  14: }

si uitaţi ce query curat se execută:

 

NHibernate: SELECT count(Id) FROM Orders WHERE CustomerId=@p0;@p0 = 1

 

 

Bun, şi această parte s-a terminat, nu au fost tratate toate combinaţiile posibile de atribute pentru <bag/>, dar s-a pus accentul pe anumite probleme clasice, minimul pentru a putea începe experimentarea pe cont propriu. Două ultime lucruri:

  • Până în acest moment echivalenţa clasă-tabel s-a manifestat întotdeauna, dar nu va fi mereu aşa.
  • Să ne uităm la query-urile pe care le generează NH iniţial pentru a înţelege ce se întâmplă, dar tot mai mult trebuie să învăţăm să dăm ca sigur comportamentul şi să ne uităm la query-uri pentru motive de optimizare.

Descarca Sursa
Parte 3
Saturday, 25 September 2010 11:54:55 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
hbm2net | NHibernate | NHibernate Tutorial

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, 25 September 2010 11:30:00 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
hbm2net | NHibernate | NHibernate Tutorial

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

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

 

Mergi la partea a 2-a: Crearea unui proiect

Mi-a venit ideea de a produce o serie de post-uri ce va permite acelora care vor să înceapă să lucreze cu NHibernate, sau cel puţin să încerce să vadă dacă acest OR/M reprezintă alegerea potrivită, să creeze în mod ordonat un cod curat, cunoscând bine de ce este nevoie, evitând parcursul „încurcat” care adesea se face la început.

Iniţial trebuie să descărcăm NHibernate. Locul potrivit pentru a face acest lucru este proiectul sourceforge, iar modul potrivit pentru a o face este procurarea fişierelor binary necesare. Mergând la linkul de download, veţi vedea ceva de genul:

clip_image002

După cum vedeţi există mai multe versiuni şi un release, ce în acest caz este reprezentat de 3.0.0 Alpha2. Eu am încercuit versiunea 2.1.2 GA pentru că este aceea pe care o vom folosi pentru această serie de articole, dar am încercuit şi GA: “general availability”. Deci, de ce nu am descărcat ultima versiune? Ce înseamnă GA ? NHibernate are o politică de predare bine consolidată şi marchează cu GA versiunile de „producţie”. Deci dacă nu cunoaşteţi anumite funcţii care există numai în alfa „de serviciu”, dacă nu vreţi să vă fie schimbate sub nas funcţii si comportamente, descărcaţi cele mai recente versiuni GA pe care le găsiţi. Pentru moment nu există „Setup”-uri ale NHibernate, cred că au existat câteva în trecut, dar oricum chiar dacă găsiţi ceva, sugestia mea este să le evitaţi: NH vă permite să faceţi un private deploy, iar acest lucru ne va fi folositor în faza de predare a aplicaţiei pe maşinile de producţie. Să descărcăm deci fişierul NHIbernate-2.1.2.GA-bin.zip. Odată descărcat, layout-ul conţinutului este schematizat mai jos:

clip_image004

În root, pe lângă ceva fleacuri, găsim două foldere importante de care avem nevoie pentru a pune la treabă NHibernate: Required_Bins, and Required_For_LazyLoading. Toate dll-urile conţinute în fişierul required bin trebuie să fie eliberate împreună cu aplicaţia (pe lângă faptul că este necesar în fază de programare J ), în timp ce din directorul Required_For_LazyLoading se poate alege folder-ul de distribuit în baza unui proxy generator ales. Acesta este un subiect pe care îl vom discuta mai târziu, pentru moment e de ajuns să ştim că NHibernate poate deriva în interior câteva dintre clasele noastre când hotărâm să avem entităţi „Lazy”, iar acest lucru se obţine prin librării externe de generare de clase „proxy”. Tipul de proxy generator ce trebuie folosit depinde de configurare, şi de aceeaşi configurare depinde şi ce folder distribuim. În exemple vom folosi LinFu. Să spunem că această alegere are impact scăzut în viaţa proiectului vostru, mai puţin în anumite situaţii particulare, deci pentru moment putem continua.

În fază de dezvoltare sunt foarte importante fişierele nhibernate-mapping.xsd si nhibernate-configuration.xsd. Aceste două fişiere vă permit să aveţi intellisens-ul abilitat pentru fișierele de mapping şi pentru configurare şi deci sunt absolut indispensabile. Deşi au ajuns în directorul required_bin, practic sunt inutile în fază de distribuţie. Pentru a activa intellisense-ul trebuie să copiaţi cele două fişiere de mai sus în directorul cuvenit din visual studio:

clip_image006

Atenţie:se poate întâmpla ca dintr-o dată Visual Studio să nu vă mai furnizeze intellisens-ul: acest lucru se întâmplă adesea deoarece există mai multe versiuni ale aceleiaşi scheme XML asociate. Aruncați o privire la acest post, unde se explică cum se poate rezolva problema.

În plus, pentru a programa şi pentru a urma exemplele în acest tutorial va fi nevoie de NUnit. Dacă îl aveţi deja, folosiţi-l, dacă nu îl aveţi puteţi recupera ultima versiune de pe site-ul oficial. Eu de obicei descarc binarul, ci nu setup-ul, şi îl decompactez sub fişierul programe. Şi NUnit funcţionează cu simplul deploy Xcopy.

Ultimul lucru înainte de a începe: când lucraţi cu NHibernate există câteva activităţi repetitive care ar fi bine să le evitaţi. Vă sfătuiesc să descărcaţi tool-ul hbm2net ce ne va permite generarea automată a claselor în cursul tutorial. Tool-ul se află în proiectul NHContrib. Descărcaţi şi decompactaţi fişierul zip într-un folder, de exemplu în C:\hbm2net. Tool-ul este un compilator linie de comandă şi va fi lansat automat de Visual Studio, dar pentru ca acest lucru să se întâmple trebuie să adăugaţi directorul unde aţi pus hbm2net în variabila de mediu PATH. Pentru a face acest lucru mergeţi la „Caracteristicile Sistemului”:

clip_image008

iar apoi adăugaţi path-ul de mai sus variabilei PATH:

clip_image010

În Partea a 2-a vom vedea cum se organizează un proiect cu NH.

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

# Friday, 24 September 2010

Può servire in molte occasioni  avere un server web *senza* installare IIS e senza dover fare hosting di ASP.NET, ma di avere un “plain vanilla” server Http, che risponda direttamente alle chiamate socket, che sia facilmente estendibile con dei moduli custom, semplice ed affidabile. Su codeplex si trova C# WebServer , il deploy nella situazione minimale consta di una sola dll ( che bellezza ! ), e metterlo in funzione si fa con un paio di righe di codice:

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         // create a server.
   6:         var server = new Server();
   7:  
   8:         // same as previous example.
   9:         server.Resources.Add(new FileResources("/", Environment.CurrentDirectory + "\\files\\"));
  10:         server.Add(new FileModule(server.Resources, false));
  11:  
  12:         // use one http listener.
  13:         server.Add(HttpListener.Create(IPAddress.Any, 8085));
  14:  
  15:         // add our own module.
  16:         server.Add(new Streamer());
  17:  
  18:  
  19:         // start server, can have max 5 pending accepts.
  20:         server.Start(5);
  21:  
  22:         Console.ReadLine();
  23:     }
  24: }

La ridotta esigenza di dipendenze ne fa uno strumento ideale per arricchire di UI le applicazioni “service” che andiamo a scrivere.

Friday, 24 September 2010 21:04:32 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
Programmin

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
Nike Winkels Nederland Outlet Nike Nederland Store Outlet Nike Nederland 2015 Outlet Nike Outlet Online Nike Sneakers Outlet