Транзакции NHibernate в рамках сеансов UnitOfWork
В проекте, над которым я работаю, UnitOfWork определен для всего сеанса (что, как представляется, является стандартной практикой для сайтов MVC + NHibernate)
Что я должен сделать, так это уметь циклически перебирать коллекцию элементов и вставлять их один за другим в свою собственную "локальную" транзакцию.
что-то вроде этого:
foreach(var item in CollectionOfItems)
{
using (ITransaction transaction = UnitOfWork.CurrentSession.BeginTransaction())
{
//do work on item. rollback on failure,
//but it should not affect the other items
}
}
Но это не сработает, потому что строка BeginTransaction использует тот же "внешний" сеанс. Как я могу получить автономный "локальный" сеанс для выполнения транзакции с небольшим блоком кода? Я думаю, что сессия вводится в единицу работы в следующем коде. Я не знаю точно, как, хотя:
Класс UnitOfWOrk имеет следующий конструктор
private readonly ISessionFactory _sessionFactory;
private readonly ITransaction _transaction;
public UnitOfWork(ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
CurrentSession = _sessionFactory.OpenSession();
_transaction = CurrentSession.BeginTransaction();
}
Он зарегистрирован через:
For<IUnitOfWork>().LifecycleIs(new HybridLifecycle())
.Use<UnitOfWork>();
Поэтому всякий раз, когда контроллер возвращает ответ, сеанс сбрасывается. Это где я запутался. Я не всегда хочу, чтобы все было всем или ничем.
РЕДАКТИРОВАТЬ:
Вот весь код для процесса регистрации NHibernate
public NHibernateRegistry()
{
FluentConfiguration fluentConfig = Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008.ShowSql().ConnectionString(x => x.FromConnectionStringWithKey("conn")))
.ProxyFactoryFactory(typeof (ProxyFactoryFactory).AssemblyQualifiedName)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<CustomerMap>());
Configuration configuration = fluentConfig.BuildConfiguration();
ConfigureNhibernateValidator(configuration);
ISessionFactory sessionFactory = fluentConfig.BuildSessionFactory();
For<Configuration>().LifecycleIs(new HybridLifecycle()).Singleton().Use(configuration);
For<ISessionFactory>().LifecycleIs(new HybridLifecycle()).Singleton().Use(sessionFactory);
For<ISession>().LifecycleIs(new HybridLifecycle())
.Use(x => x.GetInstance<ISessionFactory>().OpenSession());
For<IUnitOfWork>().LifecycleIs(new HybridLifecycle())
.Use<UnitOfWork>();
For<ITssPrincipal>().HybridHttpOrThreadLocalScoped().Use(container => BuildUserInstanceFromThreadCurrentPrincipal());
Scan(x =>
{
x.TheCallingAssembly();
x.WithDefaultConventions();
});
}
РЕДАКТИРОВАТЬ: пример проблемы DI В приведенном ниже примере вы видите, что RepoA является DI'd с RepoB, и оба получают UnitOfWork, предоставляемый StructureMap.
public RepoA(IUnitOfWork unitOfWork, ITssPrincipal principal,
IRepoB repoB)
{
}
public RepoB(IUnitOfWork unitOfWork, ITssPrincipal principal)
{
}
Даже если я создам новый сеанс в функции в RepoA, repoB все равно будет использовать исходный сеанс UnitOfWork
1 ответ
Похоже, ваш веб-сайт MVC использует StructureMap в качестве контейнера для внедрения зависимостей, что позволяет очень легко делать то, что вы хотите.
У вас есть несколько вариантов, но проще всего будет просто запросить новый экземпляр ISession из StructureMap. Это вернет вам новую и другую ISession, чем та, которую использует UnitOfWork.
Вот пример:
var session = StructureMap.ObjectFactory.GetInstance<ISession>();
using ( var tx = session.BeginTransaction() )
{
try
{
// Do your work here
tx.Commit();
}
catch ( Exception )
{
tx.Rollback();
throw;
}
}
Поскольку вы используете DI-контейнер, вы также можете использовать внедрение зависимостей StructureMap и внедрить новую ISession в конструктор вашего контроллера / класса / репозитория и т. Д., Чтобы вам не пришлось вызывать ObjectMactory.GetInstance StructureMap<>() метод.