Most friends who use NHibernate for web development know the Session-Per-Request mode, but there are not many examples of how to use it correctly on the Internet. Many articles on the Internet, including in the garden, have made the same mistake, and this mistake has been spreading...
Let’s first look at an article by Flyear in the garden, "NHibernate One Session Per Request Simple Implementation".
There is nothing wrong with configuring NHibernate first:
<property name='current_session_context_class'>web</property>
The error is in the class NHinbernateSessionFactory (the class names are all wrong). NHinbernateSessionFactory.GetCurrentSession should not include operations on HttpContext. GetCurrentSession should actually be very simple. Please see (the class name is changed to NHibernateHelper, short):
public sealed class NHibernateHelper
{
public static readonly ISessionFactory SessionFactory;
staticNHibernateHelper()
{
SessionFactory = new Configuration()
.Configure()
.AddAssembly(/**/)
.BuildSessionFactory();
}
public static ISession GetCurrentSession()
{
return SessionFactory.GetCurrentSession();
}
}
Configure current_session_context_class as web, and NHibernate will generate an instance of the NHibernate.Context.WebSessionContext class during initialization. The source code of the WebSessionContext class is as follows:
[Serializable]
public class WebSessionContext : MapBasedSessionContext
{
//Fields
private const string SessionFactoryMapKey = "NHibernate.Context.WebSessionContext.SessionFactoryMapKey";
// Methods
public WebSessionContext(ISessionFactoryImplementor factory) : base(factory)
{ }
protected override IDictionary GetMap()
{
return (HttpContext.Current.Items[SessionFactoryMapKey] as IDictionary);
}
protected override void SetMap(IDictionary value)
{
HttpContext.Current.Items[SessionFactoryMapKey] = value;
}
}
WebSessionContext implements the Session-Per-Request mode, which encapsulates HttpContext, so we do not need to operate HttpContext in our auxiliary class (NHibernateSessionFactory or NHibernateHelper).
We just need to get the Session from the instance of WebSessionContext. Obtaining the current ISession from the WebSessionContext class is quite simple, because WebSessionContext implements the ICurrentSessionContext interface:
public interface ICurrentSessionContext
{
ISession CurrentSession();
}
Classes and interfaces in the NHibernate.Context namespace
(Note: current_session_context_class can also be configured as Managed_web, Call, thread_static, corresponding to the classes ManagedWebSessionContext, CallSessionContext, and ThreadStaticSessionContext respectively)
In actual use, we do not need to directly call the CurrentSession() method of WebSessionContext, because ISessionFactory provides a simpler method that allows us to obtain the Session in one step:
public interface ISessionFactory : IDisposable
{
ISession GetCurrentSession();
//......
}
Let’s discuss the specific implementation of the ISessionFactory.GetCurrentSession method:
The Configuration.BuildSessionFactory method actually returns an instance of the SessionFactoryImpl class. Let's take a brief look at some of the code of SessionFactoryImpl:
1 public sealed class SessionFactoryImpl
2: ISessionFactoryImplementor, IMapping, ISessionFactory, IDisposable, IObjectReference
3 {
4 private readonly ICurrentSessionContext currentSessionContext;
5
6 public ISession GetCurrentSession()
7 {
8 if (this.currentSessionContext == null)
9 {
10 throw new HibernateException(
11 "No CurrentSessionContext configured (set the property current_session_context_class)!");
12}
13 return this.currentSessionContext.CurrentSession();
14}
15
16 public ICurrentSessionContext CurrentSessionContext
17 {
18 get { return this.currentSessionContext; }
19}
20
21 private ICurrentSessionContext BuildCurrentSessionContext()
twenty two {
23 string name = PropertiesHelper.GetString("current_session_context_class", this.properties, null);
24 string str2 = name;
25 if (str2 != null)
26 {
27 if (str2 == "call") return new CallSessionContext(this);
28 if (str2 == "thread_static") return new ThreadStaticSessionContext(this);
29 if (str2 == "web") return new WebSessionContext(this);
30 if (str2 == "managed_web") return new ManagedWebSessionContext(this);
31}
32 else
33 return null;
34 try
35 {
36 Type type = ReflectHelper.ClassForName(name);
37 return (ICurrentSessionContext)Environment.BytecodeProvider.ObjectsFactory
38.CreateInstance(type, new object[] { this });
39 }
40 catch (Exception exception)
41 {
42 log.Error("Unable to construct current session context [" + name + "]", exception);
43 return null;
44}
45 }
46 //......
47 }
When SessionFactoryImpl is instantiated, it calls the BuildCurrentSessionContext() method (line 21) to assign a value to the currentSessionContext field. The specific value is determined by the current_session_context_class in the configuration file.
ISessionFactory's GetCurrentSession() calls the ICurrentSessionContext's CurrentSession() (line 6) method.
The above code is quite simple, everyone must already understand it.
There are two inappropriate places in the article "NHibernate One Session Per Request Simple Implementation":
1. Not every request requires a Session to access the database. The code of Global.asax in the article performs WebSessionContext.Bind() at the beginning of all requests, which will waste a lot of Session. Although NHibernate's Session is lightweight, a more reasonable approach is to "really need" time binding.
2. Because the WebSessionContext.Unbind method requires an instance of the ISessionFactory interface, we are forced to use our helper class (NHibernateSessionFactory or NHibernateHelper) to expose the SessionFactory.
The first question is easier to solve, so leave it in the reply to communicate with everyone.
I will answer the second question in the next essay.
I have not been learning NHibernate for a long time and am a novice. If there are any mistakes, please correct me!