How to make this thread-safe - fluent-nhibernate

I have the following SessionFactory for Fluent NHibernate.
I am getting an error of
An invalid or incomplete configuration was used while creating a SessionFactory.
with an InnerException of
An item with the same key has already been added.
This problem is only happening occasionally and my application works fine most of the time.
Based on NHibernate: System.Argument Exception : An item with the same key has already been added I'm guessing my class is not thread-safe which would explain the intermittent nature of this error.
using System;
using NHibernate;
using NHibernate.Cache;
using NHibernate.Cfg;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using WSS.Data.Domain;
namespace WSS.Data {
public static class SessionFactory {
private static ISessionFactory _factory = null;
private static ISessionFactory GetFactory() {
if (_factory == null) {
NHibernate.Cfg.Configuration config;
config = new NHibernate.Cfg.Configuration();
config.Configure();
if (config == null) {
throw new InvalidOperationException("NHibernate configuration is null.");
}
config.AddAssembly("WSS.Data");
_factory = config.BuildSessionFactory();
if (_factory == null) {
throw new InvalidOperationException("Call to Configuration.BuildSessionFactory() returned null.");
}
}
return _factory;
}
private static ISessionFactory GetFluentFactory() {
if(_factory == null) {
_factory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2000
.ConnectionString(c => c
.Is(ConnectionStrings.Auto))
.Cache(c => c
.UseQueryCache()
.ProviderClass())
.ShowSql())
.Mappings(m => m
.FluentMappings.AddFromAssemblyOf())
.BuildSessionFactory();
}
return _factory;
}
public static ISession OpenSession() {
ISession session;
session = GetFluentFactory().OpenSession();
if (session == null) {
throw new InvalidOperationException("Call to factory.OpenSession() returned null.");
}
return session;
}
}
}

The usual approach is to create a mutex (probably in your public method) that only allows single access. See http://msdn.microsoft.com/en-us/library/system.threading.mutex.aspx
Not tested as compiling, but something like:
private static Mutex _sessionMutex = new Mutex();
public static ISession OpenSession() {
ISession session;
_sessionMutex.WaitOne();
session = GetFluentFactory().OpenSession();
if (session == null) {
throw new InvalidOperationException("Call to factory.OpenSession() returned null.");
}
_sessionMutex.ReleaseMutex();
return session;
}

Related

"Invalid object name"Error in fluent nhibernate configuration

I'm using fluent nhibernate in my application and I also use sql server 2012. this is my configuration:
public class SessionFactory
{
private static ISessionFactory _sessionFactory;
public static string ConnectionString
{
get { return ConfigurationManager.ConnectionStrings["ECommerceConnectionString"].ToString(); }
}
private static void Initialize()
{
var config = Fluently.Configure().Database(
MsSqlConfiguration
.MsSql2008
.ConnectionString(ConnectionString).ShowSql().Dialect<MsSql2012Dialect>());
_sessionFactory = config.Mappings(m => m.FluentMappings.AddFromAssemblyOf<ContactMapping>()).BuildSessionFactory();
}
private static ISessionFactory GetSessionFactory()
{
if (_sessionFactory == null)
Initialize();
return _sessionFactory;
}
private static ISession GetNewSession()
{
return GetSessionFactory().OpenSession();
}
public static ISession GetCurrentSession()
{
var sessionStorageContainer = SessionStorageFactory.GetStorageContainer();
var currentSession = sessionStorageContainer.GetCurrentSession();
if (currentSession == null)
{
currentSession = GetNewSession();
sessionStorageContainer.Store(currentSession);
}
return currentSession;
}
}
although I'm using MsSql2012Dialect but I still got the sql server compatibility error, how can I fix this please??
You should really use
Fluently.Configure()
.Database(
MsSqlConfiguration
.MsSql2012
.ConnectionString(conString)
.ShowSql)
works for me fluently
The error "Invalid object name" usually references to a SQL exception that for example a table does not exist or something like that... A full stack trace/error message would help.

Fluent nhibernate throws TypeInitializationException when trying to get ISession

I have this code that uses NHibernate
public bool ValidateUser(string username, string password)
{
bool loginResult;
using (var session = SessionFactory.Session)
{
session.BeginTransaction();
var makeQuery = session.Query<User>().SingleOrDefault(x => x.Username == username && x.Password == password);
loginResult = makeQuery != null;
}
return loginResult;
}
My SessionFactory looks like this
using NHibernate;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using WebSite.DatabaseModels;
using System;
namespace GameServer.Repository
{
public class SessionFactory
{
private static string connString = System.Configuration.ConfigurationManager.ConnectionStrings["MySQLConnectionString"].ConnectionString;
private static ISessionFactory session;
private static object syncRoot = new Object();
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(MySQLConfiguration
.Standard
.ConnectionString(connString))
.Mappings(m => m.FluentMappings
.AddFromAssemblyOf<UserMap>())
.ExposeConfiguration(UpdateSchema)
.BuildSessionFactory();
}
private static void UpdateSchema(Configuration cfg)
{
new SchemaUpdate(cfg);
}
public static ISession Session
{
get
{
if (session == null)
{
lock (syncRoot)
{
if (session == null)
session = CreateSessionFactory();
}
}
return session.OpenSession();
}
}
^
I tried putting a break point at the Session property, but that aint hit, but the exception is thrown at using (var session = SessionFactory.Session) and cant really see how I can fix it in error report
It would be better if you pasted the actual exception string (entire exception with stack trace) instead of an image.
As you can see in your stack trace, the exception happens in cctor of your SessionFactory class. cctor is a static constructor. Since you don't have one explicitly, your code probably breaks on initializing static fields of SessionFactory class.
My best bet would be that you don't have a connection string named MySQLConnectionString in your config file. In that case:
// this is null
ConfigurationManager.ConnectionStrings["MySQLConnectionString"];
// this will throw NullReferenceException
ConfigurationManager.ConnectionStrings["MySQLConnectionString"].ConnectionString;

NHibernate multi-threading issue

I have a ASP.NET MVC application for which I want to write some stress tests in sense of concurrent database access from multiple threads. I wrote it as a unit test using Parallel.ForEach(), but was not able to make it work since I was getting the following exception most of the time:
There is already an open DataReader associated with this Command which must be closed first
So I simplified the test as much as possible and here it is
[Test]
public void Can_Access_DB_Concurrently()
{
Parallel.ForEach(Enumerable.Range(0, 9), x =>
{
try
{
var sessionBuilder = new HybridSessionBuilder();
var session = sessionBuilder.GetSession();
using (ITransaction transaction = session.BeginTransaction())
{
var job = session.Query<Job>().Where(y => y.Name == "TestProject").SingleOrDefault().Name;
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId + ": Job name is " + job);
transaction.Commit();
}
}
catch (Exception e)
{
Trace.WriteLine(Thread.CurrentThread.ManagedThreadId + ": Exception: " + e.Message);
}
});
}
Typical output:
13: Exception: Object reference not set to an instance of an object.
16: Exception: There is already an open DataReader associated with this Command which must be closed first.
9: Exception: There is already an open DataReader associated with this Command which must be closed first.
16: Exception: There is already an open DataReader associated with this Command which must be closed first.
14: Exception: There is already an open DataReader associated with this Command which must be closed first.
The HybridSessionBuilder looks like this:
public class HybridSessionBuilder : ISessionBuilder
{
private static ISessionFactory _sessionFactory;
private static ISession _currentSession;
public ISession GetSession()
{
ISessionFactory factory = getSessionFactory();
ISession session = getExistingOrNewSession(factory);
return session;
}
private ISessionFactory getSessionFactory()
{
lock (this)
{
if (_sessionFactory == null)
{
Configuration configuration = GetConfiguration();
_sessionFactory = configuration.BuildSessionFactory();
}
return _sessionFactory;
}
}
public Configuration GetConfiguration()
{
string connectionString = WebConfigurationManager.ConnectionStrings["StagingDatabase"].ConnectionString;
Configuration configuration = new Configuration();
configuration = PostgreSQLConfiguration.PostgreSQL82
.ConnectionString(connectionString)
.Dialect("NHibernate.Dialect.PostgreSQL82Dialect")
.UseReflectionOptimizer()
.AdoNetBatchSize(50)
.ConfigureProperties(new Configuration());
configuration.AddMappingsFromAssembly(typeof(Job).Assembly);
Fluently.Configure(configuration);
return configuration;
}
private ISession getExistingOrNewSession(ISessionFactory factory)
{
{
if (HttpContext.Current != null)
{
ISession session = GetExistingWebSession();
if (session == null)
{
session = openSessionAndAddToContext(factory);
}
else if (!session.IsOpen)
{
session = openSessionAndAddToContext(factory);
}
return session;
}
if (_currentSession == null)
{
_currentSession = factory.OpenSession();
}
else if (!_currentSession.IsOpen)
{
_currentSession = factory.OpenSession();
}
}
return _currentSession;
}
public ISession GetExistingWebSession()
{
return HttpContext.Current.Items[GetType().FullName] as ISession;
}
private ISession openSessionAndAddToContext(ISessionFactory factory)
{
ISession session = factory.OpenSession();
HttpContext.Current.Items.Remove(GetType().FullName);
HttpContext.Current.Items.Add(GetType().FullName, session);
return session;
}
}
Apparently I am doing something wrong with this concurrent access, but I am unable to spot the error. Thank you for any advice.
HybridSessionBuilder is storing the ISession in a static member and therefore reusing it for each thread. The simplest solution to fix your tests would be to remove the static keyword from _currentSession. Each instance of HybridSessionBuilder would then provide a different ISession.

NHibernate: System.Argument Exception : An item with the same key has already been added

I'm getting a sporadic error that is difficult to reproduce. My first guess is that somehow I have a leaking nhibernate session, however when I ran the nhibernate profiler, I didn't see much out of the ordinary.
MVC 2.0
Fluent version 1.1.0.685
NHibernate version 2.1.2.4000
Exception: System.ArgumentException :
An item with the same key has already
been added.
Stack Trace: at
System.Collections.Generic.Dictionary2.Insert(TKey
key, TValue value, Boolean add) at
NHibernate.Util.ThreadSafeDictionary2.Add(TKey
key, TValue value) at
NHibernate.SqlTypes.SqlTypeFactory.GetTypeWithLen[T](Int32
length, TypeWithLenCreateDelegate
createDelegate) at
NHibernate.Type.EnumStringType..ctor(Type
enumClass, Int32 length)
I am using a repository model. Here's my repository class.
public sealed class Repository<T> : IRepository<T> where T : CoreObjectBase
{
#region IRepository<T> Members
private ISession Session
{
get
{
return new SessionHelper().GetSession();
}
}
public IQueryable<T> GetAll()
{
return (from entity in Session.Linq<T>() select entity);
}
public T GetById(int id)
{
return Session.Get<T>(id);
}
public void Save(params T[] entities)
{
using (ITransaction tx = Session.BeginTransaction())
{
for (int x = 0; x < entities.Count(); x++)
{
var entity = entities[x];
entity.Validate();
Session.SaveOrUpdate(entities[x]);
if (x == entities.Count() - 1 || (x != 0 && x % 20 == 0)) //20 is the batch size
{
Session.Flush();
Session.Clear();
}
}
tx.Commit();
}
}
public void SaveWithDependence<K>(T entity, K dependant) where K : CoreObjectBase
{
entity.Validate();
dependant.Validate();
using (ITransaction tx = Session.BeginTransaction())
{
Session.SaveOrUpdate(entity);
Session.SaveOrUpdate(dependant);
tx.Commit();
}
}
public void Save(T entity)
{
entity.Validate();
using (ITransaction tx = Session.BeginTransaction())
{
Session.SaveOrUpdate(entity);
tx.Commit();
}
}
public void Delete(T entity)
{
using (ITransaction tx = Session.BeginTransaction())
{
Session.Delete(entity);
tx.Commit();
}
}
public T GetOne(QueryBase<T> query)
{
var result = query.SatisfyingElementFrom(Session.Linq<T>());
return result;
//return query.SatisfyingElementFrom(Session.Linq<T>());
}
public IQueryable<T> GetList(QueryBase<T> query)
{
return query.SatisfyingElementsFrom(Session.Linq<T>());
}
/// <summary>
/// remove the sepcific object from level 1 cache so it can be refreshed from the database
/// </summary>
/// <param name="entity"></param>
public void Evict(T entity)
{
Session.Evict(entity);
}
#endregion
}
And here is my session helper, adapted from this.
public sealed class SessionHelper
{
private static ISessionFactory _sessionFactory;
private static ISession _currentSession;
public ISession GetSession()
{
ISessionFactory factory = getSessionFactory();
ISession session = getExistingOrNewSession(factory);
return session;
}
private ISessionFactory getSessionFactory()
{
if (_sessionFactory == null)
{
_sessionFactory = BuildSessionFactory();
}
return _sessionFactory;
}
private ISessionFactory BuildSessionFactory()
{
return Fluently.Configure().Database(
FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2005
.ConnectionString(c => c
.FromConnectionStringWithKey("MyDatabase"))
.AdoNetBatchSize(20))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<SessionHelper>())
.BuildSessionFactory();
}
private ISession getExistingOrNewSession(ISessionFactory factory)
{
if (HttpContext.Current != null)
{
ISession session = GetExistingWebSession();
if (session == null)
{
session = openSessionAndAddToContext(factory);
}
else if (!session.IsOpen)
{
session = openSessionAndAddToContext(factory);
}
return session;
}
if (_currentSession == null)
{
_currentSession = factory.OpenSession();
}
else if (!_currentSession.IsOpen)
{
_currentSession = factory.OpenSession();
}
return _currentSession;
}
public ISession GetExistingWebSession()
{
return HttpContext.Current.Items[GetType().FullName] as ISession;
}
private ISession openSessionAndAddToContext(ISessionFactory factory)
{
ISession session = factory.OpenSession();
HttpContext.Current.Items.Remove(GetType().FullName);
HttpContext.Current.Items.Add(GetType().FullName, session);
return session;
}
}
Any ideas or suggestions to avoid this issue?
Problem is, that SessionHelper isn't thread-safe. It will potentially build several session factories (it's a bad implementation of Singleton), which in turn probably causes the error you're seeing.
I recommend using SharpArchitecture as guidance instead.
I ran into the same issue, "An item with the same key has already been added" while constructing the nhibernate configuration.
What was happening for me was that two threads were programmatically constructing different configurations, intended to connect to different databases, at the same time.
I added a lock around the entire configuration-maker, and the problem went away.
So I guess the configuration object depends on some internal global state, i.e. assumes that the configuration itself is a singleton (as i guess it would be, if it were totally file-driven, as opposed to programmatically constructed).
I realize that this is an old question but I had a similar error just a few days ago, using NHibernate 3.0.
For readers that may stumble upon this issue: this is the result of a known thread-safety problem in older versions of NHibernate. This was fixed in version 3.2 but older versions will not have the fix and may produce this problem. This bug entry describes the issue: https://nhibernate.jira.com/browse/NH-3271
I went the route that #joel truher wrote about. But, I wanted to put the code here on how to do it.
public class NHibernateBootstrapper
{
private static readonly object _sessionFactoryLock = new object();
private static ISessionFactory _sessionFactory;
public static ISessionFactory CreateThreadStaticSessionFactory(string connectionString, bool exportSchema)
{
lock (_sessionFactoryLock)
{
if (_sessionFactory == null)
{
_sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString)
.AdoNetBatchSize(16))
.CurrentSessionContext<ThreadStaticSessionContext>()
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<NHibernateBootstrapper>()
.Conventions.AddFromAssemblyOf<NHibernateBootstrapper>();
m.HbmMappings.AddFromAssemblyOf<NHibernateBootstrapper>();
})
.ExposeConfiguration(cfg => BuildSchema(cfg, exportSchema))
.BuildSessionFactory();
}
return _sessionFactory;
}
}
}
Obviously, you can configure your database however you'd like.

FluentNHibernate and Not Recognizing the Session

For some reason the Nhibernate is chocking out when I try to access the session. It is throwing the following exception:
No CurrentSessionContext configured (set the property current_session_context_class)!
Please note I am not using XML to setup the configuration!
I am opening the session in my test:
[SetUp]
public void Initialize()
{
_session = GetSessionFactory().OpenSession();
_transaction = _session.BeginTransaction();
SetupContext();
When();
}
and then I am using Repository to access the current session. The repository is in different dll.
public void Save(Category newCategory)
{
var session = SessionFactory.GetSession();
session.SaveOrUpdate(newCategory);
}
public static ISession GetSession()
{
var session = _sessionFactory.GetCurrentSession();
if (session == null)
return _sessionFactory.OpenSession();
return session;
}
UPDATE:
In my BaseTest.cs class I also have a teardown:
[TearDown]
public void CleanUp()
{
_session.Dispose();
_transaction.Dispose();
}
During debugging it seems like the CleanUp is being fired and killing the _session object!
Another update: I have added the following code when building the configuration:
public static ISessionFactory CreateSessionFactory()
{
_sessionFactory =
Fluently.Configure().Database(
MsSqlConfiguration.MsSql2000.ConnectionString(
c => c.FromConnectionStringWithKey("ConnectionString")))
.Mappings(m =>
m.FluentMappings.AddFromAssemblyOf<Category>())
**.ExposeConfiguration(x =>
{
x.SetProperty("current_session_context_class",
"thread_static");
})**
.BuildSessionFactory();
return _sessionFactory;
}
Now I get the following error:
No session bound to the current context
You need to bind the session to the current context.
In the setup method:
var session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
And in the teardown method:
var session = CurrentSessionContext.Unbind(SessionFactory);
session.Dispose();