The official Fatica Labs Blog! RSS 2.0
# 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

# Monday, 27 September 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, 27 September 2010 17:09:49 (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)

Când se creează o aplicaţie unde sunt implicate mai multe procese server side, iar diferiţii agenţi trebuie să comunice între ei, este adesea necesară o strategie care să meargă dincolo de obişnuitele canale WCF/Remoting raw; este nevoie de un mecanism fiabil şi simplu. Mai jos găsiţi un scenariu exemplificativ, pentru a descrie problematica:

 

 

 

 

image

 

În exemplu, un server apropiat producţiei ştie când un produs s-a terminat, şi comunică acest eveniment printr-un bus. Depozitul este desigur interesat de acest eveniment şi „subscrie „ acest lucru. În acelaşi fel, depozitul poate publica că, având în vedere noul produs ce a sosit cu un stoc înainte, acum nu mai este nimic disponibil, mesaj ce cu siguranţă interesează serverul e-commerce, şi aşa mai departe pentru alte evenimente alţi agenţi.

 

Acum întrebarea ar putea fi:De ce nu conectăm diferiţii agenţi cu anumite cereri, de exemplu WCF? Sau cu cereri normale Web Service pe http, sau remoting? Motivele bune sunt:

  • Când agenţii sunt în număr mare, aceştia nu pot fi în contemporan activi. Dacă trebuie să restartez indiferent di ce motiv server-ul depozitului, nu pot să stau să-mi fac griji pentru avizele asupra produselor terminate care sosesc din producţie.

  • Elaborarea pentru un eveniment ar putea cere mult timp, pot să fac să aştepte aplicaţia care la detectat ?

  • Soluţia ar putea creşte, iar alţi agenţi ar putea fi interesaţi de evenimentele pe care le ridică alte aplicative.

In acest moment, un candidat ideal pare să fie MSMQ (sau un alt mecanism de queuing), dar şi în acest caz folosirea crudă a instrumentului poate duce la o situaţie puţin elegantă din punct de vedere al design-ului. Avem deci nevoie de un mecanism care să ne permită să lucrăm cu publicare şi subscriere de mesaje care:

  • Să garanteze livrarea mesajelor către subscriitori: dacă cine publică nu primeşte excepţii, suntem siguri că subscriitorii vor primi(mai devreme sau mai târziu, în mod rezonabil) mesajul de care sunt interesaţi

  • Să garanteze că mesajele s-au primit o singură dată de la subscriitori

  • Să ofere posibilitatea de a trimite mesaje după un anumit interval de timp

  • Să fie simplu de folosit: nu vreau să scriu zeci de pagini de configurare pentru a pune pe picioare şi menţine soluţia.

 

In reţea am găsit aceste două lucruri care fac exact asta:

Din păcate amândouă sunt „puternic tipate” şi nu permit o publicare curată a evenimentelor identificate de un label cu un playload pur textual, şi nu permit nici ridicarea de evenimente prelungite în timp. De aceea am implementat o soluţie făcută în casă, care nu conţine mult dintre cele două soluţii descrise mai înainte - în special nu deţine noţiunea de "Saga", adică un mesaj cu un istoric ce se modifică în faţa altor evenimente din bus - dar ce permite transcrierea şi subscrierea cu uşurinţă a evenimentelor în modalitate plain text. Soluţia utilizează drept canal sigur MSMQ, deci este MS oriented. Ce s-ar întâmpla dacă aş vrea să intru în arhitectura de mai sus a aplicaţiilor scrise în Java ? Cateva raspunsuri le puteti gasi in acest blog excelent, in special acest articol despre Apache ActiveMQ. Pare să fie o soluţie “cutting edge” când trebuie să facem să convieţuiască aplicaţii C#JAVA.

Monday, 27 September 2010 12:56:13 (GMT Daylight Time, UTC+01:00)  #    Comments [0] - Trackback
Programmin

# 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

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