问题
I know the question of session management has been brought up in the past, but I could not find anything that helps me overcome my problem..
I have a number of repository classes (e.g CustomerRepository, ProductRepository etc.) which I resolve through Castle Windsor (Note: I am trying to apply the three calls pattern as outlined here). I figure I'd best have a session per Presenter (in my case, this is equivalent to one per form), however, the repository classes need to access the session for the currently active form.. I am not sure how I incorporate this with the fact that these repositories are resolved through windsor, since presenters are not singletons..
For example:
public class SomePresenter
{
private ISomeView view;
private ISession session;
private ICustomerRepository customerRepository;
private IOrderRepository orderRepository;
public SomePresenter(ISomeView view, ISessionFactory sessionFactory, ICustomerRepository customerRepository, IOrderRepository orderRepository)
{
this.view = view;
this.session = sessionFactory.OpenSession();
this.customerRepository = customerRepository;
this.orderRepository = orderRepository;
}
}
The repositories needs access to the session... How do I go about this using Windsor? Am I forced to manually set the session on the repositories through a property, or is there a clever Windsor trick that I'm unfamiliar with?
回答1:
Why not just inject an ISession
into your repositories instead of an ISessionFactory
?
Here is the similar code that I use with Autofac, a different IoC container:
containerBuilder
.Register(c => NHibernateContext.GetSessionFactory().OpenSession())
.As<ISession>()
.InstancePerLifetimeScope();
where NHibernateContext
is my one and only static class that configures NHibernate and holds onto an ISessionFactory
singleton.
So my repository/lookup object asks for a session:
public MyRepository(ISession session)
{
this.session = session;
}
Then my Presenter/View Model/Superivsing Controller/Whatever-The-Heck-We're-Calling-It-This-Month just gets the repository or lookup object:
public MyPresenter(IWhateverRepository repository)
{
// Look ma, the repository has an ISession and I'm none the wiser!
}
For Windsor, I think (I'm not terribly familiar with its API, you may have to tweak this but it should give you an idea) it would be something like
container.Register(
Component.For<ISession>
.UsingFactoryMethod(
x => x.Resolve<ISessionFactory>().OpenSession())
.LifeStyle.Transient);
That is, you tell the container, "When somebody asks for an ISession, run this little delegate that gets the ISessionFactory
and opens a session, then give them that ISession
instance."
But who closes the ISession
? It's up to you: you could have the repository explicitly close the ISession
in its own Dispose()
method. Or you could rely on your container to do the closing and disposing; in Autofac, I do this with ILifetimeScope
and InstancePerLifetimeScope()
; in Windsor, I believe you need to look up nested containers, such that when you dispose a child container, all of the components it created are also disposed.
In my experience, this usually means that the container leaks into at least the "main form" of my application: when it's time to create a form, it creates a new lifetime scope/nested container and shows the form. But nothing below this level knows about the container; it's just to throw a lasso around a set of components and say "get rid of all of these when the form is closed."
(This is to prevent just one big honking ISession
from being used throughout most of the application. That works fine in ASP.NET, one session per request, but in Windows Forms, as you note, it is like a ticking time bomb for stale object exceptions. Better for each "unit of work" (typically, each form or service) to have its own ISession
.)
You could alternatively design your repositories such that each method requires an ISession
to be passed in, but that seems like it'd get tedious.
Hope that gives you some ideas. Good luck!
回答2:
Why not just have one SessionProvider
with individual Data Access Objects
(DAO) for each presenter/controller? Your model is accessed through each Data Access Object
.
public sealed class SessionProvider
{
static readonly SessionProvider provider = new SessionProvider();
private static NHibernate.Cfg.Configuration config;
private static ISessionFactory factory;
static ISession session = null;
/// <summary>
/// Initializes the <see cref="SessionProvider"/> class.
/// </summary>
static SessionProvider() { }
/// <summary>
/// Gets the session.
/// </summary>
/// <value>The session.</value>
public static ISession Session
{
get
{
if (factory == null)
{
config = new NHibernate.Cfg.Configuration();
config.Configure();
factory = config.BuildSessionFactory();
}
if (session == null)
{
if (config.Interceptor != null)
session = factory.OpenSession(config.Interceptor);
else
session = factory.OpenSession();
}
return session;
}
}
}
public sealed class OrderDataControl
{
private static ILog log = LogManager.GetLogger(typeof(OrderDataControl));
private static OrderDataControl orderDataControl;
private static object lockOrderDataControl = new object();
/// <summary>
/// Gets the thread-safe instance
/// </summary>
/// <value>The instance.</value>
public static OrderDataControl Instance
{
get
{
lock (lockOrderDataControl)
{
if (orderDataControl == null)
orderDataControl = new OrderDataControl();
}
return orderDataControl;
}
}
/// <summary>
/// Gets the session.
/// </summary>
/// <value>The session.</value>
private ISession Session
{
get
{
return SessionProvider.Session;
}
}
/// <summary>
/// Saves the specified contact.
/// </summary>
/// <param name="contact">The contact.</param>
/// <returns></returns>
public int? Save(OrderItems contact)
{
int? retVal = null;
ITransaction transaction = null;
try
{
transaction = Session.BeginTransaction();
Session.SaveOrUpdate(contact);
if (transaction != null && transaction.IsActive)
transaction.Commit();
else
Session.Flush();
retVal = contact.Id;
}
catch (Exception ex)
{
log.Error(ex);
if (transaction != null && transaction.IsActive)
transaction.Rollback();
throw;
}
return retVal;
}
来源:https://stackoverflow.com/questions/3521813/nhibernate-winforms-castle-windsor-session-management