ISession is a very important concept in NHibernate since it actually handles connection, transaction and others, thus how an application manages the session can at an impressive level affects the performance.
Basically there are 2 ways to manage the session:
1.Session per method
2.Session per conversation
Session per method is common in n-tier architecture where each operation method has its own session created from SessionFactory, the method finishes its operations using the session, flushes the session and dispose it, however, open sessions and transaction frequently may reduce the performance, moreover, each method may invoke other methods which have their own session, in such situation there are “nested” sessions and transaction and it may cause a distributed transaction, when it happens, MSDTC is required on windows OS and it makes a great fall of performance.
The second method to manage session, I called session per conversation may in some way provides better performance but on the other side it can bring disaster to your application.
Here by conversation I mean a whole scope of user request, it may be an asp.net request life-cycle from BeginRequest to EndRequest, here we initialize a session in BeginRequest event, use it all cross the life-cycle and dispose it in EndRequest.
In this way the session is reused greatly, and everything seams good, but once an exception occurred in session operation, all goodness brakes, here I have a little code to provide a classic case in which the “session per conversation” will cause a “never flush successfully” problem:
First we have our database like below
Code:
CREATE DATABASE TestFromGrayZhang;
GO
USE TestFromGrayZhang;
GO
CREATE TABLE TestUser (
ID INT IDENTITY PRIMARY KEY,
[Name] NVARCHAR(50) NOT NULL
);
GO
INSERT INTO TestUser VALUES ('AA');
INSERT INTO TestUser VALUES ('BB');
INSERT INTO TestUser VALUES ('CC');
Then the entity class and mapping file:
Code:
public class User
{
public virtual int ID
{
get;
set;
}
public virtual string Name
{
get;
set;
}
}
Code:
<?xml version="1.0"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="GDev.Test.Cmd"
namespace="GDev.Test.Cmd">
<class name="User" table="[TestUser]">
<id name="ID" type="Int32" column="ID" unsaved-value="0">
<generator class="native"/>
</id>
<property name="Name" not-null="true"/>
</class>
</hibernate-mapping>
I have a method called UpdateUser which will update a user using a session:
Code:
public static void UpdateUser(User user, ISession session)
{
using (ITransaction transaction = session.BeginTransaction())
{
try
{
session.Update(user);
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
session.Evict(user);
throw;
}
}
}
Then in the Main method I build the SessionFactory, create a singleton session to use:
Code:
ISessionFactory f;
Configuration config = new Configuration();
config.AddAssembly("GDev.Test.Cmd");
f = config.BuildSessionFactory();
//Session per conversation
ISession session = f.OpenSession();
I get 2 users from the session and it works well:
Code:
User user1 = session.Get<User>(1);
User user2 = session.Get<User>(2);
I update the name of 1st user to null, which is not allowed and update it, sure an exception is thrown:
Code:
user1.Name = null; //Null is not allowed for property name
try
{
UpdateUser(user1, session); //The update method will throw exception
}
catch (Exception ex)
{
Console.WriteLine("Error in updating the 1st user");
Console.WriteLine(ex.Message);
}
Then I update the 2nd user to a valid name:
Code:
user2.Name = "XX"; //This is allowed
try
{
UpdateUser(user2, session); //But due to previous update error, this update can never be commited
}
catch (Exception ex)
{
Console.WriteLine("Error in updating the 2nd user");
Console.WriteLine(ex.Message);
}
But the session still needs to update the 1st user because the name property has changed, so it can never complete the update statement and therefore user2 can never be updated correctly
So which management method should I use, and how to avoid the defect of the “session per conversation” method?
[/code]