The official Fatica Labs Blog! RSS 2.0
# Saturday, 23 October 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 5-a

În această parte ne vom opri puţin să analizăm mai bine cum şi când se activează sesiunea (ISession) şi care sunt modalităţile cele mai corecte pentru a o face. Să vedem în mare ce face Session:

  1. Permite recuperarea, salvarea şi eliminarea entităţilor.
  2. Menţine un mapp al entităţilor organizat ca reference/ID Database
  3. Administrează un cache ( first level cache ) care, prin mapp-ul de mai sus, evită să meargă la baza de date pentru a recupera o entitate pe care o avem deja in memorie
  4. Ţine evidenţa entităţilor modificate
  5. Administrează interacţiunile cu driver-ul ADO.NET , inclusiv deschiderea închiderea conexiunilor.
  6. Ne permite să închidem şi să deschidem tranzacţiile.

Cel de-al doilea punct merită un comentariu: între diferenţele conceptuale dintre baza de date şi obiecte .Net există identitatea obiectului. În baza de date relaţia de identitate este după o ultimă analiză garantată de ID-ul rândului, în timp ce în .NET identitatea a două obiecte este după o ultimă analiză raportată la confruntarea dintre două reference. NHibernate vrea să evite existenţa acestor ambiguităţi şi susţine faptul că trebuie să existe o singură reference care să mizeze la un determinat obiect persistent: chiar dacă creăm un obiect clonă al unui obiect persistent şi incercăm, de exemplu să-l eliminăm sau să-l modificăm, această clonă nu va fi acceptată de NHibernate.

Practic când avem nevoie să interacţionăm cu obiectele persistent, avem necesitatea de a lucra în sfera unei sesiuni. Acest lucru introduce adesea nişte erori când căutăm momentul oportun pentru a deschide sesiunea:

 

Open Session: De evitat

1. De evitat deschiderea unei sesiuni pentru fiecare interacţiune pe care vrem să o avem cu stratul persistenţei ( Session-per-operation Antipattern )

2. De evitat utilizarea unei singure sesiuni pe întreaga durată a aplicaţiei. ( Session-per-application Antipattern ) Am mai spus acest lucru, dar e bine de amintit având în vedere că vorbim despre lucruri de evitat: dacă sesiunea are o anumită agilitate de uz, Session Factory trebuie să fie deschisă o singură dată în interiorul unui app domain. Nu pentru că ar exista ceva ce nu funcționează, dar pur şi simplu sesiunea factory se încarcă lent.

Dacă folosim prima strategie, ne lovim puternic de faptul că NH nu vrea să "mixeze" entităţi ce aparţin mai multor sesiuni, se pierde orice fel de beneficiu al cach-ului de prim nivel, iar obiectele lazy tind să producă cunoscuta eroare “NHibernate.LazyInitializationException: illegal access to loading collection”, datorită faptului că sesiunea care a creat obiectul a fost deja închisă, iar NH nu poate să meargă să recupereze obiectele colecţiei.

Cea de-a doua strategie este mai complicată: lucrăm cu date în "repaus", adică date ce probabil nu sunt aduse la zi cu respectivele obiecte salvate, sesiunea se îngreunează (NH devine din ce în ce mai lent dacă obiectele tratate de o sesiune sunt prea multe), nu se mai asigură o politică corectă de administrare a conexiunii la BD, care trebuie să fie deschisă doar pe timpul necesar, şi deci închisă.

Soluţia recomandată este Session per Business Transaction. O busuness transaction este, pentru a face un exemplu: „citesc prima pagină a clienţilor", "Introduc o nouă comandă", "Caut toate produsele ce încep cu XXX în depozit", etc. Deci operaţiuni autoincluse, dar nu triviale. În aplicaţii pot exista uşoare diferenţe între rich client şi Web application, în aceste din urmă realizarea acestui pattern poate converge în Session Per Request, ce constă în deschiderea unei sesiuni când se verifică evenimentul Begin request, şi închiderea acesteia la următorul End. Acest gen de contact, în general este aproape echivalent cu Session Per bussines transaction, pentru că de fapt, într-o aplicaţie web un ciclu request/response coincide cu o bussines transaction.

Putem folosi în acest scop patterm-ul Unit of Work. În sursele de exemplu am pus o versiune simplă, care utilizează ContextSession ale NH. Practic aceste Context session ne permit să împărtăşim o sesiune între mai multe funcţii ale aplicaţiei noastre, în realaţie cu un context ce poate fi, de exemplu Thread-ul curent, sau HttpContext-ul apelului la web server, sau oricare alt lucru, din punctul de vedere al configurării. Ca să ne fie mai clar, să vedem sursele:

 

 

   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: }

 

Obiectul implementează IDisposable, şi ne permite să-l utilizăm în acest fel:

 

   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: }

Practic deschid o unitate de lucru cu using new… şi, odată ieşiţi din scope, uow se închide. În interiorul acestui scope, întregul nostru cod va putea accesa sesiunea curentă folosind funcţia statică NHUow.CurrentSession. Cei mai atenţi vor observa faptul că configurarea sesiunii factory este fixă:acest lucru se întâmplă pentru a simplifica clasa UOW, pentru a o face să devină generală este de ajuns să intervenim adăugând o Session FactoryFactory, pe care UOW o va utiliza pentru a recupera Session factory - ul extern.

Dacă în acest moment am lansa unit testul de probă, am obţine următoarea excepţie:

No CurrentSessionContext configured (set the property current_session_context_class)!

Asta se întâmplă deoarece mecanismul de întreţinere al sesiunilor de context al NH este configurabil, iar noi nu am specificat nimic. După cum spune mesajul de eroare, trebuie să adăugăm în fişierul de config rândul current_session_context_class ( rândul 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>

Observaţi că nu am introdus un nume al clasei, asta pentru că există anumite shortcut pentru cei mai folosiţi context manager. Enumerăm posibili context manager:

· NHibernate.Context.WebSessionContext – Sesiunea curentă este inclusă în HttpContext. Alias web.

· NHibernate.Context.CallSessionContext - Sesiunea curentă este inclusă în CallContext. Alias call.

· NHibernate.Context.ThreadStaticSessionContext – Sesiunea curentă este în context –ul thread-ului. Alias thread_static.

Desigur, tot cu filosofia maximei extensibilităţi, putem scrie context manager-ul nostru.

In sursele de exemplu, pe lângă clasa UOW, există o clasă a Unit Test-ului NHFromScratch.Tests.Uow , care arată cum se foloseşte.

 

Mergi la partea  5
Descarca Sursa
Saturday, 23 October 2010 18:47:18 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
hbm2net | NHibernate | NHibernate Tutorial

# Sunday, 17 October 2010

 Kynetic ORM è un nuovo ORM che sfrutta le potenzialità di C# 4.0, in particolare dynamics, per consentire un utilizzo a configurazione Zero. L’autore propone un esempio completo partendo da questo modello:

 

image

E mostrando come con qualche riga di codice si realizzi di fatto l’accesso:

 

 
   1: string cn = "your connection string";
   2: var link = new KSQLLink( cn );
   3: cmd = link.From( x => x.Employees ).Where( x => x.LastName >= "C" );
   4: foreach( dynamic obj in cmd ) Console.WriteLine( "- {0}\n", obj );
 
Ci sono comunque delle facilities se si vuole creare un mapping con oggetti fortemente tipati ( ma ovviamente il codice da scrivere aumenta :-; )

Mi sembra di poter dire che sia un progetto da seguire, anche se mi sento di dire che forse incoraggia un po’ troppo l’approccio DB-First.

Sunday, 17 October 2010 09:36:22 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
ORM

# Saturday, 16 October 2010

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

Pentru o scurtă aplicaţie pe care o scriu, un generator de etichete foarte colorate, cu grafice şi alte informaţii, m-am gândit să folosesc XAML cu un user control WPF, desenat într-o imagine ( JPEG sau PNG). Cu clasa XamlReader se citeşte script-ul XAML, cu RenderTargetBitmap se obţine un rendering în memorie, şi cu ajutorul PngBitmapEncode se salvează imaginea, de exemplu în PNG. Am construit deci un „server de etichete”, bazat pe WebServer C#, despre care am vorbit aici. Problema este că, în interiorul unui serviciu, sau oricum în orice thread ce nu aparţine unei interfeţe utilizator, rendering-ul provoacă această eroare simpatică:

 

“Cannot create instance of 'UserControl' defined in assembly 'PresentationFramework,
Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
The calling thread must be STA, because many UI components require this.  Error at Line 1 Position 2”

 

XamlRender vrea ca thread-ul folosit să fie un thread STA, lucru destul de normal pentru UI, care trebuie să gestioneze interacţiunile cu utilizatorul în mod secvenţial ( message based ). Problema poate fi rezolvată pornind un nou thread în modalitate STA, şi aşteptându-l în mod sincron. Codul este acesta:

 

 

   1:   EventWaitHandle xamlLoaded = new EventWaitHandle(false,EventResetMode.ManualReset);
   2:         
   3:          private void CreateVisualInStream(IRequest request,MemoryStream ms)
   4:          {
   5:              string fileName = GetFileName(request.Uri);
   6:              Thread t = new Thread(q => StaLoadAndRenderXaml(fileName, ms));
   7:              t.SetApartmentState( ApartmentState.STA);
   8:              xamlLoaded.Reset();
   9:              t.Start();
  10:              xamlLoaded.WaitOne(TimeSpan.FromSeconds(30));
  11:          }
  12:          private void StaLoadAndRenderXaml(string fileName, MemoryStream ms)
  13:          {
  14:              if (File.Exists(fileName))
  15:              {
  16:                  using (var s = new FileStream(fileName, FileMode.Open))
  17:                  {
  18:                      logger.Warn("Loading control:" + fileName);
  19:                      var control = XamlReader.Load(s) as Control;
  20:                      int w = Convert.ToInt32(control.GetValue(Control.WidthProperty));
  21:                      int h = Convert.ToInt32(control.GetValue(Control.HeightProperty));
  22:                      control.DataContext = new DummyDataContext();
  23:                      control.Measure(new System.Windows.Size((int)control.Width, (int)control.Height));
  24:                      control.Arrange(new System.Windows.Rect(new System.Windows.Size((int)control.Width, (int)control.Height)));
  25:                      control.UpdateLayout();
  26:                      logger.Warn("Rendering control:" + fileName);
  27:                      var renderedXaml = RenderLabel(control, w, h);
  28:                      PngBitmapEncoder png = new PngBitmapEncoder();
  29:   
  30:                      png.Frames.Add(BitmapFrame.Create(renderedXaml));
  31:                      png.Save(ms);
  32:                      logger.Info("Saved rendered control. Size=" + ms.Length);
  33:                      xamlLoaded.Set();
  34:                  }
  35:              }
  36:              else
  37:              {
  38:                  xamlLoaded.Set();
  39:                  throw new InternalServerException("Failed to process file '" + fileName + "'.");
  40:              }
  41:   
  42:          }

 

 

La linia 1 s-a creat obiectul de sincronizare de care avem nevoie pentru a înţelege dacă thread-ul a terminat. La linia 7 specificăm faptul că thread-ul creat trebuie să fie de tip STA şi după ce l-am lansat îl aşteptăm la linia 10. Să observăm timeout-ul care ne permite să evităm deadlock-ul în orice situaţie. La linia 27 codul RenderLabel se asigură să facă adevăratul rendering:

 

   1:   private RenderTargetBitmap RenderLabel(Visual v, int width, int height)
   2:   {
   3:              var bmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
   4:              bmp.Render(v);
   5:              return bmp;
   6:   }
Saturday, 16 October 2010 17:54:11 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback


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, 16 October 2010 17:48:51 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
hbm2net | NHibernate | NHibernate Tutorial

# Wednesday, 29 September 2010

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

Continuare la partea a 4-a

În această parte vom vedea un alt tip de colecţie, şi cum, în general putem colecţiona obiecte ce nu sunt entităţi, adică obiecte care nu administrează pe cont propriu o identitate, şi a căror existenţă are sens doar dacă sunt conectate la entitatea cuprinzătoare. Pentru a face asta adăugăm entitatea Produs la modelul nostru, de care vom avea oricum nevoie pentru alte lucruri, şi presupunem că avem un simplu depozit, organizat pe celule, ce conţin stocul produsului în chestiune. Să mai spunem că pentru a identifica celula ne este de ajuns un singur şir. În realitate un depozit va fi mult mai complex, dar această simplificare este un pretext pentru a arăta strategia de colecţie MAP, iar într-o realitate foarte redusă ar putea chiar să aibă un sens aşa cum e făcut.

 

Să adăugăm deci mapping-ul entităţii Produs, cu un map al locaţiilor depozitului şi relativele cantităţi anexat:

   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:  

Să vedem cum este declarat <map/>, sau mai bine spus, un mod pentru a declara un map, în acest caz un map indexat cu un şir ce identifică celula depozitului, şi cu un număr care indică cantitatea produsului prezent în această celulă. În tabela ce conţine map-ul, Tag Key este referinţa la entitatea mamă (în acest caz Product), apoi avem nevoie de un indice, în acest caz este „Cell”-ul depozitului, iar valoarea, în acest caz este Quantity a produsului. Lansând unit test-ul de creare a bazei de date obţinem, pentru partea care ne interesează, următoarea schemă:

 

 

image

 

Dacă mergem să examinăm mai bine DDL-ul observăm, în partea de creare a tabelei warehouse, următorul script:

 

clip_image001

Cheia primară a tabelei este deci constituită din FK-ul tabelei parent(Product) combinată cu componenta „Index” - în acest caz Cell -. Nu am putea avea, pentru un produs, două informaţii relative aceleiaşi celule, sau locaţii de depozit.

 

Să presupunem că pentru a localiza poziţia în depozit sunt necesare, însă, două informaţii, de exemplu rând şi coloană, şi că pe lângă cantitate ne interesează să ştim data la care această cantitate a fost stocată; putem modifica mapping-ul după cum urmează:

   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>

Practic am înlocuit “<index>” cu “<composite-index>” şi “<element>” cu “<composite-element>”. Desigur putem avea şi scenarii în care doar indicele este compozit, sau doar elementul. Imediat ce lansăm compilarea hbm2net generează câteva fişiere în plus:

 

 

clip_image001[5]

 

Practic avem o clasă pentru a reprezenta „cheia” ( WarehouseLocation ) în map şi una pentru a reprezenta „elementul” ( StockInformation ). Clasa utilizată pentru cheie trebuie să poată acţiona ca atare, şi deci trebuie să efectueze oportune ovveride -uri ale metodelor Equals si  GetHashCode; suferă comportamente imprevizibile ale codului. Utilizând hbm2net acesta generează codul oportun:

   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: }

Adăugând aceste două clase la proiect putem regenera baza de date, care rezultă modificată astfel:

 

clip_image001[7]

Diagrama clase devine, pentru partea product, următoarea:

clip_image001[9] 

 

Este interesant să observăm că din punctul de vedere OR/M StockInformation si WarehouseInformation *nu* sunt entităţi. În această parte a cursului s-a dezminţit deci presupusa corespondenţă 1:1 între entităţi şi tabele.

 

Acum să scriem unit test-ul obişnuit pentru a vedea cum se comportă NH-ul în timpul CRUD-ului în această collection. Nu ne întoarcem asupra subiectului „lazy” întrucât analog partii a 4-a pentru <bag/>.

 

 

Codul unit test-ului pe care-l vom lansa este următorul:

   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: }

Care produce următorul rezultat. S-au evidenţiat punctele în care se vede cum NH accesează la elementele colecţiei:

 

image

După cum se vede, map este foarte eficient şi accesează în update/delete elemente individuale ale colecţiei. Contrar acestei afirmaţii, având în vedere faptul că structura este reprezentată în memorie printr-un dictionary, nu este posibilă ordonarea sa (chiar făcând query-ul cu un order-by, funcţionarea internă a dicţionarului ar putea schimba această ordine).

Derscarca sursa
Partea a 4-a
Partea a 6-a
Wednesday, 29 September 2010 21:37:34 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
hbm2net | NHibernate | NHibernate Tutorial

# Tuesday, 28 September 2010

Per una piccola applicazione che sto scrivendo, un generatore di etichette molto colorate, con grafici e altre informazioni sopra, ho pensato di utilizzare XAML con uno user control WPF, disegnato in una immagine ( JPEG o PNG ). Con la classe XamlReader si legge lo script XAML, con RenderTargetBitmap si ottiene un rendering in memoria, e tramite PngBitmapEncoder si salva l’immagine, per esempio in PNG. Ho quindi costruito un “server di etichette”, basato su WebServer C#, di cui ho parlato qui. Il problema è che, all’interno di un servizio, o comunque in qualsiasi thread non di interfaccia utente, il rendering provoca questo simpatico errore:

“Cannot create instance of 'UserControl' defined in assembly 'PresentationFramework,
Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'.
The calling thread must be STA, because many UI components require this.  Error at Line 1 Position 2”

XamlRender vuole che il thread usato per il rendering sia un thread STA, cosa abbastanza normale per la UI, che deve gestire le interazioni con l’utente in modo sequenziale ( message based ). Il problema può essere risolto facendo partire un nuovo thread in modalità STA, e aspettandolo in modo sincrono. Il codice è questo:

   1:   EventWaitHandle xamlLoaded = new EventWaitHandle(false,EventResetMode.ManualReset);
   2:         
   3:          private void CreateVisualInStream(IRequest request,MemoryStream ms)
   4:          {
   5:              string fileName = GetFileName(request.Uri);
   6:              Thread t = new Thread(q => StaLoadAndRenderXaml(fileName, ms));
   7:              t.SetApartmentState( ApartmentState.STA);
   8:              xamlLoaded.Reset();
   9:              t.Start();
  10:              xamlLoaded.WaitOne(TimeSpan.FromSeconds(30));
  11:          }
  12:          private void StaLoadAndRenderXaml(string fileName, MemoryStream ms)
  13:          {
  14:              if (File.Exists(fileName))
  15:              {
  16:                  using (var s = new FileStream(fileName, FileMode.Open))
  17:                  {
  18:                      logger.Warn("Loading control:" + fileName);
  19:                      var control = XamlReader.Load(s) as Control;
  20:                      int w = Convert.ToInt32(control.GetValue(Control.WidthProperty));
  21:                      int h = Convert.ToInt32(control.GetValue(Control.HeightProperty));
  22:                      control.DataContext = new DummyDataContext();
  23:                      control.Measure(new System.Windows.Size((int)control.Width, (int)control.Height));
  24:                      control.Arrange(new System.Windows.Rect(new System.Windows.Size((int)control.Width, (int)control.Height)));
  25:                      control.UpdateLayout();
  26:                      logger.Warn("Rendering control:" + fileName);
  27:                      var renderedXaml = RenderLabel(control, w, h);
  28:                      PngBitmapEncoder png = new PngBitmapEncoder();
  29:   
  30:                      png.Frames.Add(BitmapFrame.Create(renderedXaml));
  31:                      png.Save(ms);
  32:                      logger.Info("Saved rendered control. Size=" + ms.Length);
  33:                      xamlLoaded.Set();
  34:                  }
  35:              }
  36:              else
  37:              {
  38:                  xamlLoaded.Set();
  39:                  throw new InternalServerException("Failed to process file '" + fileName + "'.");
  40:              }
  41:   
  42:          }

 

 

Alla linea 1 è creato loggetto di sincronizzazione che ci serve per capire che il thread ha finito. Alla linea 7 specifichiamo che il thread creato deve essere di tipo STA, e dopo averlo lanciato lo aspettiamo alla linea 10. Notare il timeout che ci consente di evitare deadlock in ogni situazione. Alla linea 27 il codice di RenderLabel provvede a fare il rendering vero e proprio:

   1:   private RenderTargetBitmap RenderLabel(Visual v, int width, int height)
   2:   {
   3:              var bmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
   4:              bmp.Render(v);
   5:              return bmp;
   6:   }
Tuesday, 28 September 2010 09:59:37 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
Programmin | Recipes

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