Reputation: 3029
We have NHibernate 3.1 setup using Fluent NHibernate and our session's life is managed using StructureMap 2.6.1. This is within a Web Application using VB.NET (some projects are in C#).
We're getting exceptions from production that makes it sound like multiple threads are attempting to use the same connection/transaction. These only happen when connection pooling is turned on. Turning connection pooling off clears these exceptions up, but we're seeing significant performance issues, so it's a temporary fix.
When calling session.BeginTransaction()
The server failed to resume the transaction. Desc:970000004d. The transaction active in this session has been committed or aborted by another session.
When calling transaction.Rollback()
Transaction not connected, or was disconnected
When attempting to inject ISession through StructureMap. (This only seems to happen occasionally when connection pooling is turned off.)
Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
Our NHibernateRegistry for SturctureMap looks like this:
var dbConfiguration = MsSqlConfiguration.MsSql2008.ConnectionString(ModuleConfig.GetSettings().NHibernateConnectionString)
.AdoNetBatchSize(100).IsolationLevel(IsolationLevel.ReadCommitted);
var cfg = Fluently.Configure()
.Database(dbConfiguration)
.Mappings(m =>
{
m.FluentMappings.AddFromAssemblyOf<MapMarker>();
m.AutoMappings.Add(AutoMap.AssemblyOf<EntityMarker>()
.Where(x =>
x.GetInterface(typeof(ISubClassEntity).Name) == null &&
x.GetInterface(typeof(IFakeEntity).Name) == null &&
typeof(BaseEntity).IsAssignableFrom(x))
.Conventions.AddFromAssemblyOf<ConventionsMarker>()
.UseOverridesFromAssemblyOf<OverridesMarker>()
.OverrideAll(map => map.IgnoreProperties(x => !x.CanWrite && !x.Name.EndsWith("Id") && !x.PropertyType.IsEnumerable())));
})
.Cache(c => c.UseQueryCache().ProviderClass(typeof(DotNetCacheProvider).AssemblyQualifiedName));
cfg.ExposeConfiguration(x =>
{
// custom tuplizers here, removed from snippet.
x.SetProperty("adonet.batch_size", "50");
});
var sessionFactory = cfg.BuildSessionFactory();
For<ISessionFactory>().Singleton().Use(sessionFactory);
For<ISession>().HybridHttpOrThreadLocalScoped().Use(cx =>
{
var session = cx.GetInstance<ISessionFactory>().OpenSession();
session.FlushMode = FlushMode.Commit;
session.SetBatchSize(50);
return session;
});
At the end of each request, we clean up StructureMap with the following call in the Global.asax:
Sub Application_EndRequest(ByVal sender As Object, ByVal e As EventArgs)
' Make sure we dipose of all http scoped objects
ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects()
End Sub
We have a method that we pass a Func to in order to handle our transactions. This is what that code looks like:
protected virtual TResult Transact<TResult>(Func<TResult> func)
{
if (!session.Transaction.IsActive)
{
TResult result;
using (var transaction = session.BeginTransaction())
{
try
{
result = func.Invoke();
transaction.Commit();
}
catch(Exception ex)
{
// Make sure the transaction is still active...
if(session.Transaction.IsActive)
{
transaction.Rollback();
}
throw new InvalidOperationException("There was an error while executing within an NHibernate Transaction.", ex);
}
}
return result;
}
return func.Invoke();
}
In order to prevent using implicit transactions, we use this Transact method for SELECT statements. Calls to this method look like this (the session is injected via StructureMap using constructor injection):
public T Get(TId id)
{
return Transact(() => session.Get<T>(id));
}
My question is, how do we stop connections from being shared between multiple threads, causing the exceptions above? If you need more information, please let me know.
Upvotes: 0
Views: 2353
Reputation: 27944
Your problem is in your session management. Each thread should have its own session. The session object is not thread save.
Upvotes: 1
Reputation: 6876
I don't know if that's your problem, but your Transact()
method seems weird. session.Transaction
returns a new transaction if there isn't one currently. So your session.BeginTransaction()
does only start the transaction, but doesn't create it. Objects used in a using
should also be instantiated there and not before.
Upvotes: 0