The official Fatica Labs Blog! RSS 2.0
# Wednesday, December 22, 2010

The hbm2net and db2hbm tool are now updated to the latest NH version. The fastest location for download are:

The solution on sourceforge now is updated to VS 2010.

Wednesday, December 22, 2010 10:39:01 AM (GMT Standard Time, UTC+00:00)  #    Comments [0] - Trackback
db2hbm | hbm2net | NHibernate

# Saturday, October 16, 2010
Continua da parte 5

In questa parte ci fermiamo un attimo ad analizzare meglio come e quando attivare la sessione (ISession) e quali siano le modalità più corrette per farlo. Vediamo per grandi linee cosa fa Session:

  1. Consente di recuperare salvare ed eliminare le entità.
  2. Mantiene una mappa delle entità organizzata come reference/ID Database
  3. Gestisce una cache ( first level cache ) che, tramite la mappa di cui sopra, ci evita di andare sul database per recuperare un entità che abbiamo già in memoria
  4. Tiene traccia delle entità modificate
  5. Gestisce le interazioni con il driver ADO.NET sottostante, comprese l’apertura chiusura delle connessioni.
  6. Ci consente di aprire e chiudere le transazioni.

Il punto 2 merita un commento: tra le difformità concettuali tra database e oggetti .NET vi è l’identità dell’oggetto. Nel database la relazione di identità è in ultima analisi garantita dall’ID della riga, mentre in .NET l’identità di due oggetti è in ultima analisi riconducibile al confronto fra due reference. NHibernate vuole evitare che ci siano queste ambiguità, e asserisce che ci debba essere una sola reference che punti ad un determinato oggetto persistent: anche se creiamo un oggetto clone di un oggetto persistent e cerchiamo, per esempio, di cancellarlo o di modificarlo, questo clone non sarà accettato da NHIbernate.

In pratica, quando ci serve di interagire con gli oggetti persistent, abbiamo necessita di lavorare nell’ambito di una sessione. Questo introduce spesso degli errori quando cerchiamo il momento opportuno per aprire la sessione:

Open Session: Pratiche da evitare
  1. Evitare di aprire la session per ogni singola interazione che si vuole avere con lo strato di persistenza. ( Session-per-operation Antipattern )
  2. Evitare di utilizzare una sola session per l’intera durata dell’ applicazione. ( Session-per-application Antipattern )

Già lo abbiamo detto, ma è meglio ricordarlo visto che siamo in zona di cose da evitare: se la session ha comunque una certa agilità di uso, la SessionFacory deve essere aperta una volta sola all’interno di un app domain. Non che ci sia qualcosa che non funziona, ma semplicemente la session factory è lenta a caricarsi.

Se si usa la prima strategia, ci si scontra violentemente con il fatto che NH non vuole “mixare” entità appartenenti a più sessioni,  si perde qualsiasi beneficio della cache di primo livello, e gli oggetti lazy tendono a produrre il famigerato errore “NHibernate.LazyInitializationException: illegal access to loading collection”, dovuto al fatto che la sessione che ha creato l’ogegtto ormai l’abbiamo chiusa, e NH non può andare a fare il recupero degli oggetti della collection.

La seconda strategia se possibile è anche peggiore: lavoriamo con dati in “stallo”, cioè possibilmente non aggiornati con i rispettivi oggetti persistiti, la session si appesantisce ( NH diventa sempre più lento se gli oggetti trattati da una session sono troppi ), non si assicura più una corretta politica di gestione della connessione al DB,che deve essere aperta  per il tempo strettamente necessario e quindi chiusa.

La soluzione consigliata è la Session per Business Transaction. Una business transaction è, tanto per fare un esempio: “leggo  la prima pagina dei clienti”, “Inserisco un nuovo ordine”, “cerco tutti i prodotti in magazzino che iniziano per XXX”, ecc. Quindi operazioni autocontenute, ma non triviali. Nelle applicazioni ci possono essere delle leggere differenze tra rich client e Web application, in queste ultime la realizzazione di questo pattern può convergere nella Session Per Request, che consiste nell’aprire una session quando si verifica l’evento Begin request, e di chiuderla al succesivo End. Questo approccio in generale è pressoche equivalente al Session Per business transaction, perchè di fatto in un applicativo web un ciclo request/response coincide con una business transaction.

Possiamo utilizzare per questo scopo il pattern Unit of Work. Nei sorgenti di esempio ne ho messo una versione semplice, che utilizza le ContextSession di NH. In pratica le context session ci consentono di condividere una sessione tra più funzioni del nostro applicativo, in relazione ad un contesto che può essere, per esempio il Thread corrente, oppure l’HttpContext della chiamata al web server, o qualunque altra cosa, configurabilmente. Vediamo i sorgenti che così è più chiaro:

   1: public class NHUow:IDisposable
   2: {
   3:     private NHUow()
   4:     {
   5:         if (CurrentSessionContext.HasBind(sessionFactory))
   6:             throw new InvalidOperationException("Nested unit of work not allowed.");
   7:         CurrentSessionContext.Bind(sessionFactory.OpenSession());
   8:     }
   9:     
  10:     static object lockObject = new object();
  11:     public static NHUow Open()
  12:     {
  13:         lock (lockObject)
  14:         {
  15:             if (sessionFactory == null)
  16:                 CreateSessionFactory();
  17:         }
  18:         return new NHUow();
  19:     }
  20:     public static ISession CurrentSession
  21:     {
  22:         get { return sessionFactory.GetCurrentSession(); }
  23:     }
  24:  
  25:     private static ISessionFactory sessionFactory;
  26:     private static void CreateSessionFactory()
  27:     {
  28:         Configuration cfg = CreateConfiguration();
  29:         sessionFactory = cfg.BuildSessionFactory();
  30:     }
  31:  
  32:     private static Configuration CreateConfiguration()
  33:     {
  34:         Configuration cfg = new Configuration();
  35:         cfg.Configure();
  36:         // implicitamente carichiamo tutti i mapping che si trovano nell'assembly che
  37:         // contiene customer
  38:         cfg.AddAssembly(typeof(Customer).Assembly);
  39:         return cfg;
  40:     }
  41:  
  42:  
  43:     #region IDisposable Members
  44:  
  45:     public void Dispose()
  46:     {
  47:         if (!CurrentSessionContext.HasBind(sessionFactory) )
  48:         {
  49:             throw new InvalidOperationException("Invalid current session");
  50:         }
  51:         var session = sessionFactory.GetCurrentSession();
  52:         CurrentSessionContext.Unbind(sessionFactory);
  53:         if (!session.IsOpen)
  54:         {
  55:             throw new InvalidOperationException("Session closed before UOW end");
  56:         }
  57:        
  58:         if ( null != session.Transaction && session.Transaction.IsActive )
  59:         {
  60:             if (session.Transaction.WasCommitted == false && session.Transaction.WasRolledBack == false)
  61:                 session.Transaction.Rollback();
  62:         }
  63:         session.Close();
  64:     }
  65:  
  66:     #endregion
  67: }

L’oggetto implementa IDisposable, e ci permette di utilizzaro in questo modo:

   1: using (var uow = NHUow.Open())
   2: {
   3:     var transaction = NHUow.CurrentSession.BeginTransaction();
   4:     Customer c = new Customer();
   5:     c.Name = "FELIX";
   6:     c.AddressLine1 = c.AddressLine2 = "";
   7:     c.City = "XX";
   8:     c.ZipCode = "12060";
   9:     NHUow.CurrentSession.Save(c);
  10:     transaction.Commit();
  11: }

In pratica apro una unità di lavoro con using new… e, usciti dallo scope la uow si chiude. All’interno di questo scope, tutto il nostro codice potrà accedere alla session corrente usango la funzione statica NHUow.CurrentSession. I più attenti noteranno che la configurazione del session factory è fissa: questo è per semplificare la clase UOW, per renderla generale basta intervenire aggiungendo un SessionFactoryFactory, che UOW utilizzera per recuperare il session factory esterno.

Bene, se a questo punto lanciassimo lo unit test di prova, otterremmo la seguente eccezione:

No CurrentSessionContext configured (set the property current_session_context_class)!

Questo perchè il meccanismo di mantenimento delle sessioni di contesto di NH è configurabile, e noi non abbiamo specificato nulla. Come dice il messaggio di errore, bisogna aggiungere nel file di config la linea current_session_context_class ( linea 11 ):

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

Notate che non ho inserito un nome di classe, questo perchè ci sono degli shortcut per i context manager più usati. Tanto per elecare i vari possibili context manager:

  • NHibernate.Context.WebSessionContext – la sessione corrente è contenuta in HttpContext. L’alias è web.
  • NHibernate.Context.CallSessionContext - la sessione corrente è contenuta CallContext. L’alias è call.
  • NHibernate.Context.ThreadStaticSessionContext – la sessione corrente è nel context del thread. L’alias è thread_static.

Ovviamente, sempre nella filosofia della massima estendibilità, possiamo scrivere il nostro context manager.

Nei sorgenti di esempio, oltre alla classe Uow, c’è una classe di Unit Test NHFromScratch.Tests.Uow, che ne mostra l’utilizzo.

 

Torna alla parte 5
Download Sorgenti
Saturday, October 16, 2010 5:48:51 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
hbm2net | NHibernate | NHibernate Tutorial

# 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

# Wednesday, August 11, 2010

For maintenance reasons sometime the original NHForge location is not available. In such a case you can download the file containing the binary release of the tools in the original NHContrib project download location:

nhcontribdownload

Wednesday, August 11, 2010 9:10:41 PM (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
db2hbm | hbm2net | NHibernate

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