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