The official Fatica Labs Blog! RSS 2.0
# Monday, September 27, 2010
Continua da parte 4

In questa parte vedremo un altro tipo di collezione, e di come in generale possiamo collezionare oggetti che non sono entità, cioè oggetti che non gestiscono per conto proprio una identità, e che ha senso che esistano solo se collegati all’entità contenitrice. Per fare questo aggiungiamo al nostro modello l’entità Prodotto, che ci servirà comunque per altre cose, e supponiamo di avere un semplicissimo magazzino, organizzato a celle, contenenti la giacenza del prodotto in questione. Diciamo anche che per identificare la cella ci basta avere una stringa. Nella realtà un magazzino sarà molto più complesso, ma questa semplificazione è un pretesto per mostrare la strategia di collezione MAP, ed in una realtà molto piccola potrebbe anche avere un senso così come fatto.

Aggiungiamo quindi il mapping della entità Prodotto, con annessa la mappa delle locazioni di magazzino e relative quantità:

   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="Product" table="Products">
   5:     <id name="Id" type="Int32">
   6:       <generator class="native"/>
   7:     </id>
   8:     <property name="Code" type="AnsiString" />
   9:     <property name="Description" type="AnsiString"/>
  10:     <map name="Warehouse">
  11:       <key column="Product"/>
  12:       <index column="Cell" type="AnsiString"/>
  13:       <element column="Quantity" type="Int32"></element>  
  14:     </map>
  15:     <!-- details comes here -->
  16:   </class>
  17:   
  18: </hibernate-mapping>
  19:  

Vediamo come è definita <map/>, o meglio, un modo di definire una mappa, in questo caso una mappa indicizzata da una stringa che identifica la cella di magazzino, ed un numerico che indica la quantità del prodotto presente in tale cella. Il tag key è il riferimento, nella tabella che contiene la map, alla entità madre ( in questo caso Product ), poi occorre un indice, in questo caso è la “Cell” del magazzino, e il valore, in questo caso la Quantity del prodotto. Lanciando lo unit test di creazione del database otteniamo, per la parte che ci interessa, il seguente schema:

image

Se andiamo ad indagare meglio la DDL notiamo, nella parte di creazione della tabella warehouse, il seguente script:

clip_image001

La chiave primaria della tabella è quindi costituita dalla FK alla tabella parent ( Product ) combinata con la componente “Index” – in questo caso Cell –. No potremo avere, per un prodotto, due informazioni relative alla medesima cella, o locazione di magazzino.

Supponiamo che per localizzare la posizione in magazzino siano necessarie invece due informazioni, per esempio la riga e la colonna, e che oltre che la quantità ci interessi anche sapere la data a cui questa quantità è stata stoccata, possiamo modificare il mapping come di seguito:

   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="Product" table="Products">
   5:     <id name="Id" type="Int32">
   6:       <generator class="native"/>
   7:     </id>
   8:     <property name="Code" type="AnsiString" />
   9:     <property name="Description" type="AnsiString"/>
  10:     <map name="Warehouse">
  11:       <key column="Product"/>
  12:       <composite-index class ="WarehouseLocation">
  13:         <key-property name="CellRow" type="AnsiString" />
  14:         <key-property name="CellCol" type="AnsiString" />
  15:       </composite-index>
  16:       <composite-element class="StockInformation">
  17:         <property name="StockDate" type="DateTime"/>
  18:         <property name="Quantity" type="Int32"/>
  19:       </composite-element>
  20:     </map>
  21:     <!-- details comes here -->
  22:   </class>
  23:   
  24: </hibernate-mapping>

In pratica abbiamo sostituito “<index>” con “<composite-index>” e “<element>” con “<composite-element>”. Ovviamente possiamo avere anche scenari in cui ad essere composito è solo l’indice, o solo l’element. Appena lanciata la compilazione il nostro hbm2net genera qualche file in più:

clip_image001[5]

In pratica abbiamo una classe per rappresentare la “chiave” ( WarehouseLocation ) nella mappa e una per rappresentare l’”element” ( StockInformation ). La classe utilizzata per la chiave deve poter agire come tale, e pertanto deve effettuare opportuni ovveride dei metodi EqualsGetHashCode, pena comportamenti impredicibile del codice. Utilizzando hbm2net questi gener ail codice opportuno:

   1: public override bool Equals(object other)
   2: {
   3:     if (this == other)
   4:         return true;
   5:     WarehouseLocation rhs = other as WarehouseLocation;
   6:     if (rhs == null) 
   7:         return false; // null or not a WarehouseLocation
   8:     return _cellRow.Equals(rhs._cellRow) && _cellCol.Equals(rhs._cellCol);
   9: }
  10:  
  11: public override int GetHashCode()
  12: {
  13:     return _cellRow.GetHashCode() ^ _cellCol.GetHashCode();
  14: }

Aggiungendo queste due classi al progetto possiamo rigenerare il database, che risulta così modificato:

clip_image001[7]

Il diagramma classi diventa, per la parte product, il seguente:

clip_image001[9]

E’ interessante notare che dal punto di vista OR/M StockInformation e WarehouseInformation *non* sono entità. In questa parte del corso si è quindi totalmente smentità la presunta corrispondenza 1:1 tra entità e tabelle.

 

Ora scriviamo il solito unit test per vedere come si comporta NH durante il CRUD su questa collection. Non torneremo a ripetere il discorso “lazy” in quanto analogo a quello visto nella parte 4 per <bag/>.

Il codice dello unit test che lanceremo è il seguente:

   1: [Test]
   2: public void TestCrud()
   3: {
   4:    Console.WriteLine("Creating a product");
   5:    using (ISession session = sessionFactory.OpenSession())
   6:    using (ITransaction trns = session.BeginTransaction())
   7:    {
   8:        Product p = new Product();
   9:        p.Code = "P00000";
  10:        p.Description = " a sample product";
  11:        Console.WriteLine("Adding some Warehouse information");
  12:        var l1 = new WarehouseLocation() { CellCol = "A", CellRow = "1" };
  13:        p.Warehouse.Add(new KeyValuePair<WarehouseLocation, StockInformation>(l1, new StockInformation()));
  14:        p.Warehouse[l1].Quantity = 12;
  15:        p.Warehouse[l1].StockDate = DateTime.Now;
  16:        var l2 = new WarehouseLocation() { CellCol = "A", CellRow = "2" };
  17:        p.Warehouse.Add(new KeyValuePair<WarehouseLocation, StockInformation>(l2, new StockInformation()));
  18:        p.Warehouse[l2].Quantity = 42;
  19:        p.Warehouse[l2].StockDate = DateTime.Now.AddDays(-7);
  20:        Console.WriteLine("Persisting the Product");
  21:        session.SaveOrUpdate(p);
  22:        trns.Commit();
  23:    }
  24:    Console.WriteLine("Modifying warehouse information of a product");
  25:    using (ISession session = sessionFactory.OpenSession())
  26:    using (ITransaction trns = session.BeginTransaction())
  27:    {
  28:        Console.WriteLine("Retrieving product by code");
  29:        var p = session.CreateCriteria<Product>()
  30:            .Add(Expression.Eq("Code", "P00000"))
  31:            .UniqueResult<Product>();
  32:        
  33:        Console.WriteLine("Modify stored quantity on cell A1");
  34:        var l1 = new WarehouseLocation() { CellCol = "A", CellRow = "1" };
  35:        
  36:        p.Warehouse[l1].Quantity -=3;
  37:        p.Warehouse[l1].StockDate = DateTime.Now;
  38:        
  39:        Console.WriteLine("Persisting the Product");
  40:        session.SaveOrUpdate(p);
  41:        trns.Commit();
  42:    }
  43:    Console.WriteLine("deleting warehouse information of a product");
  44:    using (ISession session = sessionFactory.OpenSession())
  45:    using (ITransaction trns = session.BeginTransaction())
  46:    {
  47:        Console.WriteLine("Retrieving product by code");
  48:        var p = session.CreateCriteria<Product>()
  49:            .Add(Expression.Eq("Code", "P00000"))
  50:            .UniqueResult<Product>();
  51:  
  52:        Console.WriteLine("Modify stored quantity on cell A1");
  53:        var l1 = new WarehouseLocation() { CellCol = "A", CellRow = "1" };
  54:  
  55:        p.Warehouse.Remove(l1);
  56:  
  57:        Console.WriteLine("Persisting the Product");
  58:        session.SaveOrUpdate(p);
  59:        trns.Commit();
  60:    }
  61: }

Che produce il seguente risultato. Si sono evidenziati i punti in cui si vede come NH accede agli elementi della collezione:

image

Come si vede, map è molto efficiente ad accedere in update/delete a singoli elementi della collezione. Per contro, visto che la struttura è rappresentata in memoria tramite un dictionary, non è possibile ordinarla ( anche se si facesse la query con un order-by, il funzionamento interno del dictionary potrebbe mutare questo ordine ).

Download Sorgenti
Parte 4
Parte 6
Monday, September 27, 2010 5:09:49 PM (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