Как передать контейнер единицы работы в конструктор хранилища с помощью внедрения зависимостей - PullRequest
4 голосов
/ 23 июля 2010

Я пытаюсь выяснить, как завершить реализацию шаблона репозитория в веб-приложении ASP.NET.

В настоящее время у меня есть интерфейс репозитория для класса домена, определяющий методы, например, для. загрузка и сохранение экземпляров этого класса.

Каждый интерфейс репозитория реализован классом, который выполняет NHibernate. Замок Виндзор сортирует DI класса в интерфейсе в соответствии с web.config. Пример реализованного класса приведен ниже:

  public class StoredWillRepository : IStoredWillRepository
  {
    public StoredWill Load(int id)
    {
      StoredWill storedWill;
      using (ISession session = NHibernateSessionFactory.OpenSession())
      {
        storedWill = session.Load<StoredWill>(id);
        NHibernateUtil.Initialize(storedWill);
      }
      return storedWill;
    }

    public void Save(StoredWill storedWill)
    {
      using (ISession session = NHibernateSessionFactory.OpenSession())
      {
        using (ITransaction transaction = session.BeginTransaction())
        {
          session.SaveOrUpdate(storedWill);
          transaction.Commit();
        }
      }
    }
  }

Как указывалось в предыдущем потоке, класс репозитория должен принимать контейнер единицы работы (т.е. ISession), а не создавать его экземпляр в каждом методе.

Я предполагаю, что контейнер единицы работы будет создаваться каждой страницей aspx при необходимости (например, в свойстве).

Как мне тогда указать, что этот экземпляр рабочего контейнера должен передаваться в конструктор StoredWillRepository, когда Windsor создает его для меня?

Или этот шаблон совершенно неправильный?

Еще раз спасибо за ваш совет.

David

Ответы [ 3 ]

1 голос
/ 23 июля 2010

У меня есть постоянный фреймворк, построенный поверх NHibernate, который используется в нескольких веб-приложениях.Он скрывает реализацию NH за интерфейсом IRepository и IRepository<T> с конкретными экземплярами, предоставляемыми Unity (таким образом, я теоретически мог бы довольно просто заменить NHibernate, скажем, на Entity Framework).

Так как Unity не поддерживает (или, по крайней мере, версию, которую я использую), не поддерживает передачу параметров конструктора, кроме тех, которые сами являются инъекциями зависимостей, передача в существующую NH ISession не являетсявозможный;но я хочу, чтобы все объекты в UOW использовали один и тот же ISession.

Я решаю эту проблему, имея управляющий класс репозитория, который управляет доступом к ISession для каждого потока:

    public static ISession Session
    {
        get
        {
            lock (_lockObject)
            {
                // if a cached session exists, we'll use it
                if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
                {
                    return (ISession)PersistenceFrameworkContext.Current.Items[NHibernateRepository.SESSION_KEY];
                }
                else
                {
                    // must create a new session - note we're not caching the new session here... that's the job of
                    // BeginUnitOfWork().
                    return _factory.OpenSession(new NHibernateInterceptor());
                }
            }
        }
    }

В этом примере PersistenceFrameworkContext.Current.Items обращается к IList<object>, которыйхранится либо ThreadStatic, если не в веб-контексте, либо в HttpContext.Current.Items, если он находится в веб-контексте (во избежание проблем с пулом потоков).Первый вызов свойства создает экземпляр ISession из сохраненного экземпляра фабрики, последующие вызовы просто извлекают его из хранилища.Блокировка немного замедлит работу, но не так сильно, как простая блокировка статического экземпляра ISession в области приложения.

Затем у меня есть методы BeginUnitOfWork и EndUnitOfWork, чтобы позаботиться о UOW - у меня естьспециально запрещены вложенные UOW, потому что, честно говоря, ими было трудно управлять.

    public void BeginUnitOfWork()
    {
        lock (_lockObject)
        {
            if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
                EndUnitOfWork();

            ISession session = Session;
            PersistenceFrameworkContext.Current.Items.Add(SESSION_KEY, session);
        }
    }

    public void EndUnitOfWork()
    {
        lock (_lockObject)
        {
            if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
            {
                ISession session = (ISession)PersistenceFrameworkContext.Current.Items[SESSION_KEY];
                PersistenceFrameworkContext.Current.Items.Remove(SESSION_KEY);
                session.Flush();
                session.Dispose();
            }
        }
    }

Наконец, пара методов обеспечивает доступ к репозиториям, специфичным для типа домена:

    public IRepository<T> For<T>()
        where T : PersistentObject<T>
    {
        return Container.Resolve<IRepository<T>>();
    }

    public TRepository For<T, TRepository>()
        where T : PersistentObject<T>
        where TRepository : IRepository<T>
    {
        return Container.Resolve<TRepository>();
    }

(Здесь, PersistentObject<T> - это базовый класс, обеспечивающий поддержку идентификаторов и равных.)

Таким образом, доступ к данному репозиторию осуществляется по шаблону

NHibernateRepository.For<MyDomainType>().Save();

. Затем он отображается так, что вы можете использовать

MyDomainType.Repository.Save();

Если у заданного типа есть специализированный репозиторий (т.е. ему нужно больше, чем он может получить от IRepository<T>), тогда я создаю интерфейс, происходящий от IRepository<T>, расширяющей реализации, наследующей от моего IRepository<T>реализации, и в самом типе домена я переопределяю статическое свойство Repository, используя new

    new public static IUserRepository Repository
    {
        get
        {
            return MyApplication.Repository.For<User, IUserRepository>();
        }
    }

(MyApplication [который в реальном продукте называется чем-то менее хитрым) - этокласс фасадов, который заботится о предоставлении экземпляра Repository через Unity, чтобы у вас не было зависимости от конкретной реализации репозитория NHibernate в классах вашего домена.)

Это дает мне полную возможность подключения через Unity для реализации репозитория, легкодоступ к хранилищу в коде без скачков и прозрачного управления для каждого потока ISession.

Там намного больше кода, чем просто выше (и я значительно упростил пример кода),но у вас есть общее представление.

MyApplication.Repository.BeginUnitOfWork();
User user = User.Repository.FindByEmail("wibble@wobble.com");
user.FirstName = "Joe"; // change something
user.LastName = "Bloggs";
// you *can* call User.Repository.Save(user), but you don't need to, because...
MyApplication.Repository.EndUnitOfWork();
// ...causes session flush which saves the changes automatically

В моем веб-приложении у меня есть сеанс для запроса, поэтому BeginUnitOfWork и EndUnitOfWork вызывают в BeginRequest и EndRequest соответственно.

0 голосов
/ 23 июля 2010

Технически, ответ на мой вопрос заключается в использовании перегрузки контейнера. Решение, которое позволяет вам указать аргумент конструктора как анонимный тип:

IUnitOfWork unitOfWork = [Code to get unit of work];
_storedWillRepository = container.Resolve<IStoredWillRepository>(new { unitOfWork = unitOfWork });

Но давайте посмотрим правде в глаза, ответы, предоставленныевсе остальные были намного более информативными.

0 голосов
/ 23 июля 2010

У меня довольно похожая структура на вашу, и вот как я решаю ваш вопрос:

1) Чтобы указать свой контейнер для каждого метода, у меня есть отдельный класс (" SessionManager "), который я затем вызываю через статическое свойство.Таким образом, вот пример использования моей реализации Save:

private static ISession NHibernateSession
{
    get { return SessionManager.Instance.GetSession(); }
}

public T Save(T entity)
{
    using (var transaction = NHibernateSession.BeginTransaction())
    {
        ValidateEntityValues(entity);
        NHibernateSession.Save(entity);

        transaction.Commit();
    }

    return entity;
}

2) Мой контейнер создается не на каждой странице ASPX.Я воплощаю все свои достоинства в NHibernate на странице global.asax .

** Еще несколько вещей возникают **

3)Вам не нужно иметь помощника для создания экземпляра Load.Вы могли бы также использовать Get вместо Load.Дополнительная информация @ Разница между Load и Get .

4) Используя ваш текущий код, вам придется повторять в значительной степени один и тот же код для каждого необходимого объекта домена(StoredWillRepository, PersonRepository, CategoryRepository и т. Д.?), Который выглядит как перетаскивание.Вы могли бы очень хорошо использовать универсальный класс для работы над NHibernate, например:

public class Dao<T> : IDao<T>
{
    public T SaveOrUpdate(T entity)
    {
        using (var transaction = NHibernateSession.BeginTransaction())
        {
            NHibernateSession.SaveOrUpdate(entity);
            transaction.Commit();
        }

        return entity;
    }
}

В моей реализации я мог бы тогда использовать что-то вроде :

Service<StoredWill>.Instance.SaveOrUpdate(will);
...