Try fast search NHibernate

06 January 2009

Using Conversation per Business Transaction

The point I leave the pattern, in previous post, was its implementation.
Before show how use it with AOP, I think is better to show how it work because my AOP implementation is only one of the possible implementations and you can write yours (and hopefully share it).

Introduction

In this post, you will see a persistent-conversation which theory was introduced in here. Remember that what I want do is abstract the persistent-conversation-handling from the DAO/Repository implementation. The persistence-layer, I’ll use, is NHibernate2.1.0 (trunk) and the UoW is the NHibernate’s session. The use of NH2.1.0 mean that you must know this, by the way in this example I’ll show how use the conversation avoiding any kind of DynamicProxy, IoC and AOP framework (I hope you would use one).
Even if the target of the example is a win-form or WPF application I don’t want spent a single second writing a GUI so you will see a console application.

The example core

To understand how use the pattern implementation you should study two implementations: PersistenceConversationalModel, FamilyCrudModel
Let me show only one method:
public IList<TAnimal> GetExistingComponentsList()
{
try
{
using (GetConversationCaregiver())
{
return animalDao.GetAll();
}
}
catch (Exception e)
{
ManageException(e);
throw;
}
}

The real action here is animalDao.GetAll(); . The action is enclosed in a “Resume” – “Pause”. In the try-catch I’m discarding the persistence conversation in case of an exception.
If you don’t use AOP you must enclose all DAOs/Repository actions with the same structure (obviously you can call more than one action between  “Resume” – “Pause”).
The two methods to End or Abort the conversation are respectively:
public void AcceptAll()
{
try
{
EndPersistenceConversation();
}
catch (Exception e)
{
ManageException(e);
throw;
}
}

public void CancelAll()
{
try
{
AbortPersistenceConversation();
}
catch (Exception e)
{
ManageException(e);
throw;
}
}

From the point of view of Conversation-per-Business-Transaction usage that it’s all.

A more deep view

To have a more deep view, and understand what a possible AOP implementation must do (remember that you have one implemented and usable here), I must explain what are doing the PersistenceConversationCaregiver each time you call the method GetConversationCaregiver().


   1:         private class PersistenceConversationCaregiver : IDisposable
   2:         {
   3:             private readonly ConversationEndMode endMode;
   4:             private readonly PersistenceConversationalModel pcm;
   5:  
   6:             public PersistenceConversationCaregiver(PersistenceConversationalModel pcm, ConversationEndMode endMode)
   7:             {
   8:                 this.pcm = pcm;
   9:                 this.endMode = endMode;
  10:                 string convId = pcm.GetConvesationId();
  11:                 IConversation c = pcm.cca.Container.Get(convId) ?? pcm.cf.CreateConversation(convId);
  12:                 pcm.cca.Container.SetAsCurrent(c);
  13:                 c.Resume();
  14:             }
  15:  
  16:             #region Implementation of IDisposable
  17:  
  18:             public void Dispose()
  19:             {
  20:                 IConversation c = pcm.cca.Container.Get(pcm.GetConvesationId());
  21:                 switch (endMode)
  22:                 {
  23:                     case ConversationEndMode.End:
  24:                         c.End();
  25:                         break;
  26:                     case ConversationEndMode.Abort:
  27:                         c.Dispose();
  28:                         break;
  29:                     default:
  30:                         c.Pause();
  31:                         break;
  32:                 }
  33:             }
  34:  
  35:             #endregion
  36:         }

In the constructor (form line 6 to 14):
line 10 : I’m getting the conversationId from the “model” instance. The conversationId is a Guid in a string and the “model” is the conversationId-holder.
line 11: I’m getting the existing conversation, from the container, or I create a new one.
line 12: I’m setting the conversation as the current conversation.
line 13: I’m starting or resume the conversation.
In the dispose (from line 18 to 33):
line 20: I’m getting the conversation (it must exists)
line 30: I’m pausing the conversation.
To End or Abort the conversation the implementation is:
protected void EndPersistenceConversation()
{
IConversation c = cca.Container.Get(GetConvesationId());
if(c!=null)
{
c.Resume();
c.End();
}
}

protected void AbortPersistenceConversation()
{
IConversation c = cca.Container.Get(GetConvesationId());
if (c != null)
{
c.Abort();
}
}
Note the Resume before the End of the conversation; to be ended the conversation must be active.
The GetConvesationId() have no secrets:
protected virtual string GetConvesationId()
{
if (conversationId == null)
{
conversationId = Guid.NewGuid().ToString();
}
return conversationId;
}
What is more important, perhaps, is the ManageException method:
protected virtual void ManageException(Exception e)
{
IConversation c = cca.Container.Unbind(conversationId);
if (c != null)
{
c.Dispose();
}
}
Here I’m unbinding the conversation from the container and disposing the conversation. In term of NHibernate this mean the Rollback of the transaction and the dispose of the session. After this operation you can continue working in the same “Model” instance, what you must know is that you will use a new NHibernate-session.

The Example

The example show how the pattern work in tow cases:
  1. A simulation of an input in one Form
  2. A simulation of an input in two opened Form
If you download the code you can see a Diagram in each “layer”.
To have fun, writing the example, I have implemented a HomeMadeServiceLocator and I hope it can be useful to who are scared by IoC, Dependency Injection and AOP words.
The code of the example is available here.
If you have some question, about the example, feel free to ask.


kick it on DotNetKicks.com


7 comments:

  1. Fabio,

    I wrote a post on my take on Conversation per Business transaction could you please look over it and let me know what you think or see if you can spot anything wrong?

    http://dotnetchris.wordpress.com/2009/01/27/conversation-per-business-transaction-using-postsharp-and-ioc/

    Thanks!
    Chris Marisic

    ReplyDelete
  2. Fabio

    I really want to take a look at your example, but I can't find a way to download the sourcecode. Can you help me please?

    Thanks!
    Koen

    ReplyDelete
  3. To download sources you can use TortoiseSvn and then take a look to
    http://code.google.com/p/unhaddins/source/checkout

    ReplyDelete
  4. Hi Fabio,

    I am new to Nhibernate and ORMs in general and am trying to implement Conversation per Business Transaction in a new project. I have looked at your example and have used your PersistenceConversationalModel class and various SessionEasier classes to implement something virtually identical to your example.

    The difficulty I'm having is that while in your example, FamilyCrudModel.Save() will save the entity but not flush it to the db until you call FamilyCrudModel.AcceptAll(), when I try the same in mine, Save() flushes to the db straight away, meaning you cannot CancelAll() to discard the session changes.

    I have tried stepping through the 2 together and just cannot find any differences, Flush mode is never, I'm using the same db configuration in both. The only difference is I'm using a custom IConfigurationProvider for fluent Nh as described on http://entron.wordpress.com/ but I cannot see how that would be the problem.

    I'm not sure if this is the right place to post, but any suggestions you can give for me to explore would be greatly appreciated.

    Thanks

    Sock

    ReplyDelete
  5. @Sock
    Are you using Identity as POID strategy ?
    If yes, have a look here

    ReplyDelete
  6. Fabio,
    Thanks for the speedy reply, that was it! Saw that a while ago and had completely slipped my mind. Using HiLo now and all working perfectly. One more quick question:

    In your example, why is that after calling Save() but before AcceptAll(), GetEntirelyList() will not include the saved entity? I was under the impression that Save() would add the entity to the Session cache and so be returned as though in the database when requested, even though not actually committed to the db. I also notice that calling Get() after Save() _will_ return the entity, though it is not included when GetAll() is called. Is that incorrect or just a 'feature' of Nh?

    Thanks again for your help.

    ReplyDelete
  7. NH can resolve a query by Id looking in the session but can't resolve any other query with persistent-NOT-flushed entities-states.

    ReplyDelete