ninject вводит iunitofwork в атрибут области хранилища

Позвольте мне начать с моей текущей настройки, а затем объяснить, чего я пытаюсь достичь. Мы используем NHibernate и пытаемся реализовать шаблон IRepository/IUnitOfWork с помощью Ninject. В идеале он должен работать в общем случае для любого приложения, использующего код, будь то ASP.Net, WCF или что-то еще.

IUnitOfWork

public interface IUnitOfWork
{
    object Add(object obj);//all other supported CRUD operations we want to expose
    void Commit();
    void Rollback();
}

UnitOfWork

public class UnitOfWork : IUnitOfWork
{
    private readonly ISessionFactory _sessionFactory;
    private readonly ISession _session;
    private readonly ITransaction _transaction;

    public UnitOfWork(ISessionFactory sessionFactory)
    {
        _sessionFactory = sessionFactory;
        _session = _sessionFactory.OpenSession();
        _transaction = _session.BeginTransaction();
    }

    public object Add(object obj)
    {
        return _session.Save(obj);
    }

    public void Commit()
    {
        if(!_transaction.IsActive)
        {throw new Exception("some error");}
        _transaction.Commit();
    }

    public void Rollback()
    {
        if (!_transaction.IsActive)
        {
            throw new Exception("some other error");
        }
        _transaction.Rollback();
    }
}

IRepository

public interface IRepository<TEntity, TId> where TEntity : class
{
    TId Add(TEntity item);//add other missing CRUD operations
}

GenericRepository

public class GenericRepository<TEntity, TId> : IRepository<TEntity, TId>
    where TEntity : class
{
    public TId Add(TEntity item)
    {
        throw new NotImplementedException();
    }
}

Я использую Ninject в качестве контейнера IOC. Цель состоит в том, чтобы повторно использовать тот же IUnitOfWork для жизненного цикла создания UnitOfWork. Я хочу, чтобы реализованный жизненный цикл работал независимо от того, что является вызывающим приложением, иначе я бы просто использовал InRequestScope, как и большинство предложений в Интернете. Я смог сделать что-то вроде этого:

//constructor
public MyService(IUnitOfWork uow, IRepository<User, int> userRepo, IRepository<Cat, int> catRepo)
{
    _uow = uow; _userRepo = userRepo; _catRepo = catRepo;
}

//method in same class
public void DoSomeWork()
{
    _userRepo.Add(someUser);
    _catRepo.Add(someCat);
    _uow.Commit();

    //rollback on error
}

И мои привязки настроены так:

Bind<IUnitOfWork>.To<UnitOfWork>().InCallScope();
Bind(typeof(IRepository<,>)).To(typeof(GenericRepository<,>));

И эта обязательная конфигурация на самом деле работает для вышеупомянутого MyService, он создаст UnitOfWork один раз в конструкторе, и он будет использовать тот же UnitOfWork для импеллингов IRepo, независимо от того, сколько слоев на самом деле они могут быть.

Но то, что я хотел бы сделать, - это полностью скрыть IUnitOfWork от приложений. Я бы предпочел предоставить некоторый TransactionAttribute, который можно поместить поверх метода, и он создаст IUnitOfWork для записи, и этот же экземпляр будет внедрен во все будущие запросы для IUnitOfWork в рамках TransactionAttribute. И это позаботится о совершении и откате соответственно. Таким образом, предыдущий код станет примерно таким:

//constructor
public MyService(IRepository<User, int> userRepo, IRepository<Cat, int> catRepo)
{
    _uow = uow; _userRepo = userRepo; _catRepo = catRepo;
}

//method in same class
[Transaction]
public void DoSomeWork()
{
    _userRepo.Add(someUser);
    _catRepo.Add(someCat);
}

Могу ли я выполнить какие-либо настройки привязки, которые позволят мне пометить метод с помощью [Транзакция], подобной этой? Я открыт для некоторой незначительной реструктуризации IUnitOfWork и IRepository, и код уровня сервиса - это просто фрагмент кода, поэтому я могу быть там очень гибким.

1 ответ

Решение

Во-первых, инъекция не будет работать с

[Transaction]
public void DoSomeWork()
{
      _userRepo.Add(someUser);
      _catRepo.Add(someCat);
}

в конце концов, контейнер не может знать, какой метод вы собираетесь вызывать и когда вы собираетесь его вызывать.

Более того, поскольку вы хотите, чтобы он работал с WebApps, WCF,... может быть, с какой-то кварцевой работой или с чем-то другим: вам придется решить, хотите ли вы связать время жизни IUnitOfWork / Repository / NHibernate Session на время жизни объектов, которые его используют (например, MyService) или если вы хотите, чтобы сервис работал дольше (например, синглтон) и создал новый NHibernate Session для вызова метода, как:

[Transaction]
public void DoSomeWork()
{
    _userRepo.Add(someUser);
    _catRepo.Add(someCat);
}

На самом деле это может быть достигнуто с помощью АОП, используя Fody или PostSharp. В качестве альтернативы вы также можете использовать шаблон декоратора для достижения этой цели (см., Например, здесь). Однако, если я правильно помню, в ninject в настоящее время отсутствуют некоторые тонкости для поддержки обработки easz decorator.

В качестве альтернативы [Transaction] атрибут, который вы могли бы начать с контроля всего явно:

public interface IUnitOfWork
{
    IUnitOfWorkSession Begin(); // starts a session and transaction
}

public interface IUnitOfWorkSession : IDisposable
{
   void Commit();
   void Dispose(); // performs rollback in case no commit was performed
}

Так как вам нужно будет получить доступ к nhibernate Session в других объектах, таких как _userRepo а также _catRepo Вам также понадобится способ получить доступ к Session существует независимо от контейнера, поскольку он не имеет ничего общего с тем, как / когда вы создаете объекты. Конечно, вы могли бы передать это как:

public void DoSomeWork()
{
    using(IUnitOfWorkSession session = _unitOfWork.Begin())
    {    
        _userRepo.Add(session, someUser);
        _catRepo.Add(session, someCat);
        session.Commit();
    }
}

Но это не очень круто. Так что вместо этого вам нужно будет использовать что-то вроде ThreadLocal (если вы используете async / await, это может быть проблематично) или иначе SynchronizationContextлокальное хранилище

Теперь, если вам удалось продвинуться так далеко, вы можете заняться АОП. АОП будет иметь две ручки две вещи:

  • получение доступа к IUnitOfWork.
    • может быть сделано путем ткачества в качестве дополнительного аргумента ctor
    • или он мог бы проверить, есть ли уже один, если есть, он мог бы использовать этот, если нет, он мог бы бросить или сплетить аргумент в...
  • оборачивая декорированный метод, как в том же коде, что и выше:
public void DoSomeWork()
{
    using(IUnitOfWorkSession session = _unitOfWork.Begin()) // weave this
    {    
        _userRepo.Add(session, someUser);
        _catRepo.Add(session, someCat);
        session.Commit(); // weave this
    }
}
Другие вопросы по тегам