Общий ответ: «Тот, кто создает экземпляр ISession
, должен его утилизировать. Если транзакция не была зафиксирована, это фактически откат».
Я добился успеха, используя шаблон командыопределить операцию, которую я хочу выполнить на единицу работы.Скажем, у нас есть Person
сущность, и одна из вещей, которую мы можем сделать, это изменить имя человека.Давайте начнем с сущности:
public class Person
{
public virtual int Id { get; private set; }
public virtual string Name { get; private set; }
public virtual void ChangeName(string newName)
{
if (string.IsNullOrWhiteSpace(newName))
{
throw new DomainException("Name cannot be empty");
}
if (newName.Length > 20)
{
throw new DomainException("Name cannot exceed 20 characters");
}
this.Name = newName;
}
}
Определите простую команду POCO следующим образом:
public class ChangeNameCommand : IDomainCommand
{
public ChangeNameCommand(int personId, string newName)
{
this.PersonId = personId;
this.NewName = newName;
}
public int PersonId { get; set; }
public string NewName { get; set; }
}
... и обработчик для команды:
public class ChangeNameCommandHandler : IHandle<ChangeNameCommand>
{
ISession session;
public ChangeNameCommandHandler(ISession session)
{
// You could demand an IPersonRepository instead of using the session directly.
this.session = session;
}
public void Handle(ChangeNameCommand command)
{
var person = session.Load<Person>(command.PersonId);
person.ChangeName(command.NewName);
}
}
Цель состоит в том, чтобы код, существующий вне области Session / Work, мог делать что-то вроде этого:
public class SomeClass
{
ICommandInvoker invoker;
public SomeClass(ICommandInvoker invoker)
{
this.invoker = invoker;
}
public void DoSomething()
{
var command = new ChangeNameCommand(1, "asdf");
invoker.Invoke(command);
}
}
Вызов команды подразумевает «выполнить эту команду на единицу работы».Вот что мы хотим сделать, когда вызываем команду:
- Начать вложенную область IoC (область «Единица работы»)
- Запустить сеанс IS и транзакцию (этовероятно подразумевается как часть шага 3)
- Разрешить
IHandle<ChangeNameCommand>
из области действия IoC - Передать команду обработчику (домен выполняет свою работу)
- Подтвердитьтранзакция
- Завершение области действия IoC (единица работы)
Итак, вот пример использования Autofac в качестве контейнера IoC:
public class UnitOfWorkInvoker : ICommandInvoker
{
Autofac.ILifetimeScope scope;
public UnitOfWorkInvoker(Autofac.ILifetimeScope scope)
{
this.scope = scope;
}
public void Invoke<TCommand>(TCommand command) where TCommand : IDomainCommand
{
using (var workScope = scope.BeginLifetimeScope("UnitOfWork")) // step 1
{
var handler = workScope.Resolve<IHandle<TCommand>>(); // step 3 (implies step 2)
handler.Handle(command); // step 4
var session = workScope.Resolve<NHibernate.ISession>();
session.Transaction.Commit(); // step 5
} // step 6 - When the "workScope" is disposed, Autofac will dispose the ISession.
// If an exception was thrown before the commit, the transaction is rolled back.
}
}
Примечание: UnitOfWorkInvoker
, который я показал здесь, нарушает SRP - это UnitOfWorkFactory
, UnitOfWork
и Invoker
все в одном.В моей реальной реализации я разбил их.