So here is the problem. I have a common class library that holds all the repositories, domain and mapping files so the library can be reused within other web beased applications. Now within this class library there is a peiece of code that allows itself to create a session factory to be used in its repositories. Code looks something like this.
private static ISessionFactory sessionFactory;
private static Configuration configuration;
public static Configuration Configuration()
{
if (configuration == null)
{
configuration = new Configuration().Configure();
}
return configuration;
}
private static ISessionFactory SessionFactory
{
get
{
if (sessionFactory == null)
{
sessionFactory = Configuration().BuildSessionFactory();
}
return sessionFactory;
}
}
public static ISession GetCurrentSession()
{
if (!CurrentSessionContext.HasBind(SessionFactory))
{
CurrentSessionContext.Bind(SessionFactory.OpenSession());
}
return SessionFactory.GetCurrentSession();
}
So the repository calls the GetCurrentSession() method to get a ISession. Now this works fine but I am worried that it might not be thread safe. Can any one help me with an approach that will help me make it thread safe.
Few things to Note:
I have thought about configuring and building the SessionFactory in global.asax of the web applications on start event but the problem with this is that the common class library in question is used within 20 different applications so this will mean going to all the applications and updating the global.asax file now before I do this I wanted to put the question out there to see if there any other ways I can go about this. So that the common class library can configure its SessionFactory itself and yet be thread safe.
Thanks for reading this huge question. Will appericate any help.
The session factory is threadsafe, the session is not. Building the session factory needs to be protected:
private static object lockObject = new object();
private static ISessionFactory SessionFactory
{
get
{
lock (lockObject)
{
if (sessionFactory == null)
{
sessionFactory = Configuration().BuildSessionFactory();
}
return sessionFactory;
}
}
}
The session factory is created the first time a thread is requesting a session. This needs to be thread safe to avoid creating the session factory multiple times.
Creating the session by the session factory is thread safe, so you don't need to worry about that.
Sessions are not thread safe in NHibernate by design. So it should be ok as long as you have a session used by only one thread.
You can have one NHibernate SessionFactory for multiple threads as long as you have a separate NHibernate session for each thread
for more info have a look at the below link:
https://forum.hibernate.org/viewtopic.php?p=2373236&sid=db537baa5a57e3968abdda5cceec2a24
I suggest use one session for each Request like this:
public ISession GetCurrentSession()
{
HttpContext context = HttpContext.Current;
var currentSession = context.Items["session"] as ISession;
if( currentSession is null )
{
currentSession = SessionFactory.GetCurrentSession()
context.Items["session"] = currentSession;
}
return currentSession;
}
Following on from the comment by Stefan Steinegger, I think it would be more efficient to add a null check immediately before the lock, that way you don't need to lock every time if the sessionFactory has already been initialized.
private static object lockObject = new object();
private static ISessionFactory SessionFactory
{
get
{
if (sessionFactory != null)
{
return sessionFactory;
}
lock (lockObject)
{
if (sessionFactory == null)
{
sessionFactory = Configuration().BuildSessionFactory();
}
return sessionFactory;
}
}
}
Related
It's my understanding -- e.g. from sources like First and Second Level caching in NHibernate -- that NHibernate's ISession.get<T>(object id) should, when using the "default" setup -- session, etc., return the same instance if called twice with the same id. However, I'm getting two separate instances.
I've seen vaguely-similar questions but no useful results with searches like this, and this.
Here's my get method:
BillingItem IEntityRepository.GetBillingItemByID(int id)
{
var session = Helpers.NHibernateHelper.OpenSession();
using (ITransaction tran = session.BeginTransaction())
{
var ret = session.Get<BillingItem>(id);
tran.Commit();
return ret;
}
}
Here's my test, which is failing:
var repo = (IEntityRepository) new SqliteEntityRepository();
var bi1 = repo.GetBillingItemByID(26);
var bi2 = repo.GetBillingItemByID(26);
Assert.AreSame(bi1, bi2); // fails
Here's NHibernateHelper just in case you want to see it:
internal static class NHibernateHelper
{
private static ISessionFactory _sessionFactory;
internal static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
private static ISessionFactory SessionFactory
{
get
{
if (_sessionFactory == null)
{
var configuration = new Configuration();
configuration.Configure();
configuration.AddAssembly(typeof(BillingItem).Assembly);
configuration.AddAssembly(typeof(PaymentItem).Assembly);
var mapper = new ModelMapper();
mapper.AddMappings(typeof(Mappings.BillingItemMapping).Assembly.GetExportedTypes());
mapper.AddMappings(typeof(Mappings.PaymentItemMapping).Assembly.GetExportedTypes());
var mapping = mapper.CompileMappingForAllExplicitlyAddedEntities();
configuration.AddDeserializedMapping(mapping, null);
SchemaMetadataUpdater.QuoteTableAndColumns(configuration);
_sessionFactory = configuration.BuildSessionFactory();
}
return _sessionFactory;
}
}
}
What am I missing here?
This must be true, because in a snippet above we are using ... almost anti-pattern ... a very short session:
using (ISession session = Helpers.NHibernateHelper.OpenSession())
{ ... }
That is not what we usually need. We need a Unit of Work session. In web app, it usually last through whole request... (In a desktop... there should be some UoW workaround).
So, if there are two different sessions - then both produce different run-time instance.
Repositories should not be responsible for handling transactions. You need to have a single instance of unit of work that would allow you to run multiple queries in the same session/transaction.
It looks to me that the OpenSession() method creates a new session each time. Can you post the code for it?
I would like to create an NHibernate session factory once at the start of a SpecFlow test run, and then access it in individual step definitions to call OpenSession() on it.
It seems like a [BeforeTestRun] hook would be the best place to set up the session factory. However I am struggling to see how I can store the session factory and then retrieve it in a particular step definition (most likely part of a Background section) in order to get a session and insert some data.
I tried using the SpecFlow container, as follows:
[Binding]
public class NhDataSupport
{
private readonly IObjectContainer objectContainer;
public NhDataSupport(IObjectContainer objectContainer)
{
this.objectContainer = objectContainer;
}
[BeforeTestRun]
public void InitializeSessionFactory()
{
var sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2012.ConnectionString(c => c.FromConnectionStringWithKey("SqlServerDataTesting")))
.Mappings(cfg =>
cfg.FluentMappings.AddFromAssemblyOf<HostMap>()
)
.BuildSessionFactory();
objectContainer.RegisterInstanceAs<ISessionFactory>(sessionFactory);
}
}
...so that other [Binding] classes could be passed the session factory via constructor injection, I hoped. But this gets a
System.Reflection.TargetException, Non-static method requires a target.
I'm guessing that's because (as I learned from the SpecFlow docs), the method [BeforeTestRun] is applied to must be static.
Is there a way of achieving this, configuring the SessionFactory once but calling OpenSession on it from other Binding classes? I don't want to build the session factory for every scenario, as this is an expensive operation.
The following works.
Use a static field on a non-static [Binding]-annotated class.
In [BeforeTestRun], do the work (in my case building the SessionFactory) and assign the result to the static field.
In [BeforeScenario], register the static field instance with the container.
Not sure if it's best practice, but it does work.
[Binding]
public class DataHooks
{
private readonly IObjectContainer objectContainer;
private static ISessionFactory sessionFactory;
public DataHooks(IObjectContainer objectContainer)
{
this.objectContainer = objectContainer;
}
[BeforeTestRun]
public static void SetupNhibernateSessionFactory()
{
sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2012.ConnectionString(c => c.FromConnectionStringWithKey("SqlServerDataTesting")))
.Mappings(cfg =>
cfg.FluentMappings.AddFromAssemblyOf<HostMap>()
)
.BuildSessionFactory();
}
[BeforeScenario]
public void BeforeScenario()
{
objectContainer.RegisterInstanceAs<ISessionFactory>(sessionFactory);
}
}
The session factory is then available in any [Binding]-annotated class via constructor injection of ISessionFactory.
You could do something like this:
public class SessionFactoryHolder
{
private static ISessionFactory sessionFactory;
public static void SetupNhibernateSessionFactory()
{
sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2012.ConnectionString(c => c.FromConnectionStringWithKey("SqlServerDataTesting")))
.Mappings(cfg => cfg.FluentMappings.AddFromAssemblyOf<HostMap>() )
.BuildSessionFactory();
}
public ISessionFactory SessionFactory
{
get { return sessionFactory; }
}
}
[Binding]
public class Binding
{
[BeforeTestRun]
public static void SetupNhibernateSessionFactory()
{
SessionFactoryHolder.SetupNhibernateSessionFactory();
}
}
Now you can access the SessionFactory when you let SpecFlow inject the SessionFactoryHolder via constructor.
It is similar to #ngm solution, but you can spare to get the "internal" IObjectContainer from SpecFlow.
See here http://www.specflow.org/documentation/Context-Injection/ for more infos about context injection in SpecFlow.
Notice: code written by head, not tried to compile, so there could be typos.
While this is not specific to NHibernate I ran into a similar issue trying to maintain authorization throughout a entire test run of API tests. I ended up using a singleton pattern for my rest client using the [BeforeScenario] tag. While this isn't [BeforeTestRun] that this question is asking about and the object will still be registered before each scenario, the amount of times the client is created is still limited to once. I would imagine you could apply a similar approach to NHibernate.
[Binding]
public class RestClientInjector
{
private readonly IObjectContainer objectContainer;
public RestClientInjector(IObjectContainer objectContainer)
{
this.objectContainer = objectContainer;
}
[BeforeScenario]
public void InitializeRestClient()
{
RestClient client = SingletonRestClient.getInstance();
objectContainer.RegisterInstanceAs<RestClient>(client);
}
// implelent singleton
public class SingletonRestClient
{
private static RestClient client = new RestClient();
private SingletonRestClient(IObjectContainer objectContainer) {}
public static RestClient getInstance()
{
return client;
}
}
}
I have a web application where many components are registered using .LifestylePerWebRequest(), now I've decided to implement Quartz.NET, a .NET job scheduling library, which executes in separate threads, and not the Request thread.
As such, HttpContext.Current yields null. My services, repositories, and IDbConnection were instanced so far using .LifestylePerWebRequest() because it made it easier to dispose of them when the requests ended.
Now I want to use these components in both scenarios, during web requests I want them to remain unaffected, and in non-request contexts I want them to use a different Lifestyle, I figure I can handle the disposing myself, but how should I go about it for choosing a lifestyle for the components based on the current context?
Currently I register services (for example), like this:
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerWebRequest()
);
I figure I should be using some kind of extension method but I just don't see it..
You should use Hybrid Lifestyle from castleprojectcontrib.
An hybrid lifestyle is one that actually blends two underlying lifestyles: a main lifestyle and a secondary lifestyle. The hybrid lifestyle first tries to use the main lifestyle; if it's unavailable for some reason, it uses the secondary lifestyle. This is commonly used with PerWebRequest as the main lifestyle: if the HTTP context is available, it's used as the scope for the component instance; otherwise the secondary lifestyle is used.
Don't use the same components. In fact, in most scenarios I've seen the "background processing" doesn't even make sense to be in the web process to begin with.
Elaborating based on the comments.
Shoehorning background processing in the web pipeline is compromising your architecture to save a few $ on a EC2 instance. I would strongly suggest to think about this again, but I digress.
My statements still stands, even if you're putting both components in the web process they are two different components used in two different contexts and should be treated as such.
I've had a very similar problem recently - I wanted to be able to run initialisation code based off my container in the Application startup, when HttpContext.Request does not yet exist. I didn't find any way of doing it, so I modified the source of the PerWebRequestLifestyleModule to allow me to do what I wanted. Unfortunately it didn't seem possible to make this change without recompiling Windsor - I was hoping I would be able to do it in an extensible way so I could continue to use the main distribution of Windsor.
Anyway, to make this work, I modified the GetScope function of the PerWebRequestLifestyleModule so that if it was NOT running in a HttpContext (or if HttpContext.Request throws an exception, like it does in Application_Start) then it will look for a Scope started from the container instead. This allows me to use my container in Application_Start using the following code:
using (var scope = container.BeginScope())
{
// LifestylePerWebRequest components will now be scoped to this explicit scope instead
// _container.Resolve<...>()
}
There's no need to worry about explicitly disposing of things, because they will be disposed when the Scope is.
I've put the full code for the module below. I had to shuffle a couple of other things around within this class for it to work, but it's essentially the same.
public class PerWebRequestLifestyleModule : IHttpModule
{
private const string key = "castle.per-web-request-lifestyle-cache";
private static bool allowDefaultScopeOutOfHttpContext = true;
private static bool initialized;
public void Dispose()
{
}
public void Init(HttpApplication context)
{
initialized = true;
context.EndRequest += Application_EndRequest;
}
protected void Application_EndRequest(Object sender, EventArgs e)
{
var application = (HttpApplication)sender;
var scope = GetScope(application.Context, createIfNotPresent: false);
if (scope != null)
{
scope.Dispose();
}
}
private static bool IsRequestAvailable()
{
if (HttpContext.Current == null)
{
return false;
}
try
{
if (HttpContext.Current.Request == null)
{
return false;
}
return true;
}
catch (HttpException)
{
return false;
}
}
internal static ILifetimeScope GetScope()
{
var context = HttpContext.Current;
if (initialized)
{
return GetScope(context, createIfNotPresent: true);
}
else if (allowDefaultScopeOutOfHttpContext && !IsRequestAvailable())
{
// We're not running within a Http Request. If the option has been set to allow a normal scope to
// be used in this situation, we'll use that instead
ILifetimeScope scope = CallContextLifetimeScope.ObtainCurrentScope();
if (scope == null)
{
throw new InvalidOperationException("Not running within a Http Request, and no Scope was manually created. Either run from within a request, or call container.BeginScope()");
}
return scope;
}
else if (context == null)
{
throw new InvalidOperationException(
"HttpContext.Current is null. PerWebRequestLifestyle can only be used in ASP.Net");
}
else
{
EnsureInitialized();
return GetScope(context, createIfNotPresent: true);
}
}
/// <summary>
/// Returns current request's scope and detaches it from the request context.
/// Does not throw if scope or context not present. To be used for disposing of the context.
/// </summary>
/// <returns></returns>
internal static ILifetimeScope YieldScope()
{
var context = HttpContext.Current;
if (context == null)
{
return null;
}
var scope = GetScope(context, createIfNotPresent: true);
if (scope != null)
{
context.Items.Remove(key);
}
return scope;
}
private static void EnsureInitialized()
{
if (initialized)
{
return;
}
var message = new StringBuilder();
message.AppendLine("Looks like you forgot to register the http module " + typeof(PerWebRequestLifestyleModule).FullName);
message.AppendLine("To fix this add");
message.AppendLine("<add name=\"PerRequestLifestyle\" type=\"Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Windsor\" />");
message.AppendLine("to the <httpModules> section on your web.config.");
if (HttpRuntime.UsingIntegratedPipeline)
{
message.AppendLine(
"Windsor also detected you're running IIS in Integrated Pipeline mode. This means that you also need to add the module to the <modules> section under <system.webServer>.");
}
else
{
message.AppendLine(
"If you plan running on IIS in Integrated Pipeline mode, you also need to add the module to the <modules> section under <system.webServer>.");
}
#if !DOTNET35
message.AppendLine("Alternatively make sure you have " + PerWebRequestLifestyleModuleRegistration.MicrosoftWebInfrastructureDll +
" assembly in your GAC (it is installed by ASP.NET MVC3 or WebMatrix) and Windsor will be able to register the module automatically without having to add anything to the config file.");
#endif
throw new ComponentResolutionException(message.ToString());
}
private static ILifetimeScope GetScope(HttpContext context, bool createIfNotPresent)
{
var candidates = (ILifetimeScope)context.Items[key];
if (candidates == null && createIfNotPresent)
{
candidates = new DefaultLifetimeScope(new ScopeCache());
context.Items[key] = candidates;
}
return candidates;
}
}
Ok, I figured out a very clean way to do this!
First of all we'll need an implementation of IHandlerSelector, this can select a handler based on our opinion on the matter, or remain neutral (by returning null, which means "no opinion").
/// <summary>
/// Emits an opinion about a component's lifestyle only if there are exactly two available handlers and one of them has a PerWebRequest lifestyle.
/// </summary>
public class LifestyleSelector : IHandlerSelector
{
public bool HasOpinionAbout(string key, Type service)
{
return service != typeof(object); // for some reason, Castle passes typeof(object) if the service type is null.
}
public IHandler SelectHandler(string key, Type service, IHandler[] handlers)
{
if (handlers.Length == 2 && handlers.Any(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest))
{
if (HttpContext.Current == null)
{
return handlers.Single(x => x.ComponentModel.LifestyleType != LifestyleType.PerWebRequest);
}
else
{
return handlers.Single(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest);
}
}
return null; // we don't have an opinion in this case.
}
}
I made it so the opinion is very limited on purpose. I'll be having an opinion only if there are exactly two handlers and one of them has PerWebRequest lifestyle; meaning the other one is probably the non-HttpContext alternative.
We need to register this selector with Castle. I do so before I start registering any other components:
container.Kernel.AddHandlerSelector(new LifestyleSelector());
Lastly I wish I had any clue as to how I could copy my registration to avoid this:
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerWebRequest()
);
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerThread()
);
If you can figure out a way to clone a registration, change the lifestyle and register both of them (using either container.Register or IRegistration.Register), please post it as an answer here! :)
Update: In testing, I need to uniquely name the identical registrations, I did so like this:
.NamedRandomly()
public static ComponentRegistration<T> NamedRandomly<T>(this ComponentRegistration<T> registration) where T : class
{
string name = registration.Implementation.FullName;
string random = "{0}{{{1}}}".FormatWith(name, Guid.NewGuid());
return registration.Named(random);
}
public static BasedOnDescriptor NamedRandomly(this BasedOnDescriptor registration)
{
return registration.Configure(x => x.NamedRandomly());
}
I don't know whats happening behind the scenes in .LifestylePerWebRequest(); but this is what I do for "Context per request" scenarios:
Check HttpContext for session and if exists pull the context from .Items.
If it doesn't exist pull your context from System.Threading.Thread.CurrentContext.
Hope this helps.
Having defined a domain model I want to figure out how to do the rest of work.
DATA ACCESS LAYER
I had read before that it is not necessary to code own UnitOfWork implementation over ISession (thogh I found a much information on how to do it pretty well). So I'm quite confused.. I have repository interface like this:
public interface IRepository<T> where T: AbstractEntity<T>, IAggregateRoot
{
T Get(Guid id);
IQueryable<T> Get(Expression<Func<T, Boolean>> predicate);
IQueryable<T> Get();
T Load(Guid id);
void Add(T entity);
void Remove(T entity);
void Remove(Guid id);
void Update(T entity);
void Update(Guid id);
}
Where in the concrete implementation there are two options:
OPTION A
Is to inject ISessionFactory thru constructor and have something similar to:
public class Repository<T> : IRepository<T> where T : AbstractEntity<T>, IAggregateRoot
{
private ISessionFactory sessionFactory;
public Repository(ISessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
}
public T Get(Guid id)
{
using(var session = sessionFactory.OpenSession())
{
return session.Get<T>(id);
}
}
}
OPTION B
Is to use NHibernateHelper class
using(var session = NHibernateHelper.GetCurrentSession())
{
return session.Get<T>(id);
}
Where NHibernateHelper is
internal sealed class NHibernateHelper
{
private const string CurrentSessionKey = "nhibernate.current_session";
private static readonly ISessionFactory sessionFactory;
static NHibernateHelper()
{
sessionFactory = new Configuration().Configure().BuildSessionFactory();
}
public static ISession GetCurrentSession()
{
HttpContext context = HttpContext.Current;
ISession currentSession = context.Items[CurrentSessionKey] as ISession;
if(currentSession == null)
{
currentSession = sessionFactory.OpenSession();
context.Items[CurrentSessionKey] = currentSession;
}
return currentSession;
}
public static void CloseSession()
{
HttpContext context = HttpContext.Current;
ISession currentSession = context.Items[CurrentSessionKey] as ISession;
if(currentSession == null)
{
return;
}
currentSession.Close();
context.Items.Remove(CurrentSessionKey);
}
public static void CloseSessionFactory()
{
if(sessionFactory != null)
{
sessionFactory.Close();
}
}
}
What's option is prefered?
Why(besides the injection)?
If I use option A where do I place configuration of ISessionFactory?
Should it be placed somewhere in ASP.NET MVC project? How?
Thank you for reading the monster-question! Your guidance is appreciated!
How to handle injecting dependencies with mvc is somewhat version specific but it always helps to use a real Dependency Injection (DI) container. However you slice it, this solution will need you to Inject an ISession into the Repository rather than an ISessionFactory. This allows your DI container to manage the lifetime of the session properly.
Assuming you're using Asp.Net MVC 3 and dont have an attachment to a specific DI container already, fire up your Nuget console and type:
install-package Ninject.MVC3
This will go, download Ninject (which is a DI container) and configure your mvc application to use it. It will also create a file ~/App_Start/NinjectMVC3.cs which is where you'll configure your dependencies as such.
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ISessionFactory>()
.ToMethod(c => new Configuration().Configure().BuildSessionFactory())
.InSingletonScope();
kernel.Bind<ISession>()
.ToMethod((ctx) => ctx.Kernel.Get<ISessionFactory>().OpenSession())
.InRequestScope();
kernel.Bind<IRepository<>>().To<Repository<>>();
}
The first statement tells ninject that when something requires an ISessionFactory, it should lazily initialize NHibernate and create one. This session factory is then to be held as an application-wide singleton for the lifetime of the application.
The second statement tells ninject that when something requires an ISession, it should get an instance of ISessionFactory and call OpenSession(). This Session is then reused within the scope of the request and destroyed at the end of the request.
The third statement tells ninject that when something requires an IRepository of any type, it should just new one up using it's built in logic to resolve dependencies.
From here you can write your code as follows and everything should just work.
public class WidgetController : Controller
{
private readonly IRepository<Widget> _repository;
public WidgetController(IRepository<Widget> repository)
{
_repository = repository;
}
}
With regards to the Repository I'd like to point you to an excelent blog post Repository is the new Singleton
I usually use a read only property, on my repository, like this
protected ISession Session
{
get
{
return NHibernateSessionFactory.CurrentFor(dataBaseFactoryKey);
}
}
My NHibernateSessionFactory works like this.
In web apps you should use pattern NH session per web request. I think you should have only one session per web request and your repositories should use this single session.
To implement this you need to write IHttpModule which will open session, begin transaction and bind session as ambient (current) session when request begins and end transaction and close session when request ends. You also need to set current_session_context_class to "web". Then your Repository/DAO will look like this
public TEntity Get(object id)
{
return sessionFactory.GetCurrentSession().Get<TEntity>(id);
}
Best pattern with MVC and NHibernate is session per request.
steps:
In Global.asax add
public static ISessionFactory SessionFactory;
In Application_Start() configure and build session factory:
var config = new Configuration().Configure();
SessionFactory = config.BuildSessionFactory();
In Application_BeginRequest open the session and bind it to
CurrentSessionContext:
var nhSession = SessionFactory.OpenSession();
CurrentSessionContext.Bind(Session);
In Application_EndRequest() unbind and dispose the session
Now in your controller you can access your session invoking:
Global.SessionFactory.GetCurrentSession();
EDIT: Following #hival comment
Inside your controller handle your model in a using block and perform commit/rollback based on your logic.
I'm using NHibernate + Fluent to handle the database in my application. So far I've been using a SessionSource to create my ISession objects. I'm a bit confused now about what comes from NHibernate or Fluent, and what I really should use for creating my sessions.
ISession comes from NHibernate, and the SessionSource from Fluent. I create the SessionSource from a FluentConfiguration, and currently use the SessionSource to create sessions. This is my function to create sessions. The FluentConfiguration and SessionSource is reused:
if (_sessionSource == null)
{
_cfg = Fluently.Configure().Database(SQLiteConfiguration.Standard.ShowSql().UsingFile("test.db"));
_sessionSource = new SessionSource(_cfg.BuildConfiguration().Properties, new MappingsPersistenceModel());
var session = _sessionSource.CreateSession();
_sessionSource.BuildSchema(session);
return session;
}
return _sessionSource.CreateSession();
Does this look reasonable? It sounds more appealing to use a ISessionFactory to create sessions though, so I tried using one. This comes from NHibernate, so I don't know if that's why it is a problem, but it fails when my sessions are created from an ISessionFactory.
// Done once:
_sessionFactory = _cfg.BuildSessionFactory();
// Done each time a session is requested:
_sessionFactory.OpenSession()
Using this I get a MappingException when using the session, saying "No persister for: MyProject.Model.SomeModelClass".
Should I keep using the SessionSource? Or am I missing something regarding the ISessionFactory?
The problem seems to be that the SessionFactory doesn't know about the mappings since they are only given to the SessionSource. Adding the mappings during the fluent configuration and getting the factory from this seems to help. This gave me what looks like a better solution. Does this look reasonable to those of you with more experience on this?
private static ISession CreateSession()
{
if (_sessionFactory == null)
{
_sessionFactory = Fluently.Configure().
Database(SQLiteConfiguration.Standard.ShowSql().UsingFile("test.db")).
Mappings(m => m.FluentMappings.AddFromAssemblyOf<MappingsPersistenceModel>()).
BuildSessionFactory();
}
return _sessionFactory.OpenSession();
}
Please see this class may be help full for you.I have write methods for create factory and session.
public class Common
{
public const string NHibernateSessionKey = "nhibernate.session.key";
public static string ConnString
{
get
{
return System.Configuration.ConfigurationManager.ConnectionStrings["JewelSoftMySqlConn"].ConnectionString;
}
}
public static ISessionFactory FACTORY = CreateFactory();
static ISessionFactory CreateFactory()
{
Configuration config = new Configuration();
IDictionary props = new Hashtable();
props.Add("hibernate.dialect", "NHibernate.Dialect.MySQLDialect");
props.Add("hibernate.connection.provider", "NHibernate.Connection.DriverConnectionProvider");
props.Add("hibernate.connection.driver_class", "NHibernate.Driver.MySqlDataDriver");
props.Add("hibernate.connection.connection_string", Common.ConnString);
config.AddProperties(props);
config.AddInputStream(new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(Resource.Models_hbm)));
return config.BuildSessionFactory();
}
public static ISession GetCurrentSession()
{
ISession currentSession = null;
HttpContext context = HttpContext.Current;
if (context != null)
{
currentSession = context.Items[NHibernateSessionKey] as ISession;
if (currentSession == null)
{
currentSession = FACTORY.OpenSession();
context.Items[NHibernateSessionKey] = currentSession;
}
}
else//will work non web request, like in test environment
currentSession = FACTORY.OpenSession();
return currentSession;
}
}
I know how you feel! The divide between fluent and NH can be rather confusing at the start. In my opinion you should not use SessionSource, AFAIK it is only really useful in testing scenarios. I recomend that you just use the ISessionFactory directly from NH.
Can you post your error? You seem to be using it correctly, so probably there is something wrong with the configuration or the cfg object.