Nhibernate, WinForms, Castle Windsor: управление сеансом

Я знаю, что вопрос об управлении сеансом поднимался в прошлом, но я не мог найти ничего, что помогло бы мне преодолеть мою проблему.

У меня есть несколько классов репозитория (например, CustomerRepository, ProductRepository и т. Д.), Которые я разрешаю через Castle Windsor (Примечание: я пытаюсь применить шаблон трех вызовов, как описано здесь). Я полагаю, что лучше всего иметь сеанс для каждого Presenter (в моем случае это эквивалентно одному сеансу для каждой формы), однако классам репозитория необходимо получить доступ к сеансу для текущей активной формы .. Я не уверен, как я включаю это с тем, что эти репозитории разрешаются через windsor, так как презентеры не синглтоны ..

Например:

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;
  }
}

Репозиториям нужен доступ к сеансу ... Как мне это сделать с помощью Виндзора? Принужден ли я вручную устанавливать сеанс в репозиториях через свойство, или есть хитрый виндзорский трюк, с которым я не знаком?


person Morten Jacobsen    schedule 19.08.2010    source источник
comment
Для протокола, я открыт для предложений, отличных от текущего подхода.   -  person Morten Jacobsen    schedule 21.08.2010


Ответы (2)


Почему бы просто не вставить ISession в свои репозитории вместо ISessionFactory?

Вот аналогичный код, который я использую с Autofac, другим контейнером IoC:

containerBuilder
    .Register(c => NHibernateContext.GetSessionFactory().OpenSession())
    .As<ISession>()
    .InstancePerLifetimeScope();

где NHibernateContext - мой единственный статический класс, который настраивает NHibernate и хранит ISessionFactory синглтон.

Итак, мой объект репозитория / поиска запрашивает сеанс:

public MyRepository(ISession session)
{
    this.session = session;
}

Затем мой Presenter / View Model / Superivsing Controller / Whatever-The-Heck-We-Calling-It-This-Month просто получает репозиторий или объект поиска:

public MyPresenter(IWhateverRepository repository)
{
     // Look ma, the repository has an ISession and I'm none the wiser!
}

Я думаю, что для Виндзора (я не очень хорошо знаком с его API, возможно, вам придется его настроить, но это должно дать вам представление) это будет что-то вроде

container.Register(
    Component.For<ISession>
    .UsingFactoryMethod(
        x => x.Resolve<ISessionFactory>().OpenSession())
    .LifeStyle.Transient);

То есть вы говорите контейнеру: «Когда кто-то запрашивает ISession, запустите этого маленького делегата, который получит ISessionFactory и откроет сеанс, а затем передайте ему этот ISession экземпляр».

Но кто закрывает ISession? Выбор за вами: вы можете настроить репозиторий, явно закрывающий ISession в его собственном Dispose() методе. Или вы можете положиться на ваш контейнер для закрытия и утилизации; в Autofac я делаю это с ILifetimeScope и InstancePerLifetimeScope(); Я считаю, что в Виндзоре вам нужно искать вложенные контейнеры, чтобы при удалении дочернего контейнера также удалялись все созданные им компоненты.

По моему опыту, это обычно означает, что контейнер проникает, по крайней мере, в «основную форму» моего приложения: когда приходит время создать форму, он создает новую область действия / вложенный контейнер и показывает форму. Но ничто ниже этого уровня не знает о контейнере; просто бросить лассо вокруг набора компонентов и сказать: «Избавьтесь от всего этого, когда форма будет закрыта».

(Это сделано для того, чтобы в большей части приложения не использовалось только одно большое гудок ISession. Это отлично работает в ASP.NET, один сеанс на запрос, но в Windows Forms, как вы заметили, это похоже на бомбу замедленного действия для устаревших исключения объектов. Лучше для каждой «единицы работы» (обычно для каждой формы или услуги) иметь свою собственную ISession.)

В качестве альтернативы вы можете спроектировать свои репозитории так, чтобы каждый метод требовал передачи ISession, но это кажется утомительным.

Надеюсь, это даст вам несколько идей. Удачи!

person Nicholas Piasecki    schedule 21.08.2010
comment
Похоже на разумный подход ... Тем не менее, я вижу одну проблему (если я не неверно истолковываю ваши намерения;)) .. Что, если Mypresenter нуждается в CustomerRepository и OrderRepository, и в этом случае им нужно использовать один и тот же сеанс .. На основе в вашем коде (поправьте меня, если я ошибаюсь), будет открываться новый сеанс для каждого репозитория? - person Morten Jacobsen; 22.08.2010
comment
В Autofac есть концепция продолжительности жизни. Поэтому я бы позаботился о том, чтобы зависимости для моего докладчика были разрешены в одной и той же области жизни, чтобы они использовали одну и ту же сессию ISession. В Виндзоре вам нужно искать вложенные / дочерние контейнеры. Я почти уверен, что Виндзор поддерживает это, но я не уверен в его терминологии. using (var childScope = this.container.BeginLifeTimeScope ()) {// В этой области создается один сеанс ISession var presenter = this.childScope.Resolve ‹MyPresenter› (); // делаем что-нибудь} // здесь размещается ISession - person Nicholas Piasecki; 22.08.2010

Почему бы просто не иметь один SessionProvider с индивидуальным Data Access Objects (DAO) для каждого докладчика / контроллера? Доступ к вашей модели осуществляется через каждый 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;
        }
person Community    schedule 19.08.2010
comment
Я не большой поклонник этого подхода, поскольку он, кажется, делает код менее тестируемым ... Я бы предпочел найти чистый способ распространения соответствующего объекта сеанса в репозитории .. - person Morten Jacobsen; 19.08.2010