Well in a web application a unit of work is responsible for the transaction management.
But what about a windows application?
As far as I know the repository is the connector between my data access layer and my business layer. It hides all the data access stuff from my business layer.
Using this fact let me think of taking all the transaction stuff into the repository.
But I read that having Commit/RollBack methods on the repository is violating the repository's intent.
I ask myself who is responsible for transaction management in a non web application and how do I hide the transaction/Nhibernate stuff from the business layer?
The general answer is "Whoever instantiates the ISession
should dispose of it. If the transaction has not been committed, this is effectively a rollback."
I've had success by using the command pattern to define an operation that I want to perform on a unit of work. Say we have a Person
entity and one of the things we can do is change a person's name. Let's start with the entity:
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;
}
}
Define a simple POCO Command like this:
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; }
}
...and a Handler for the command:
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);
}
}
The goal is that code that exists outside of a Session/Work scope can do something like this:
public class SomeClass
{
ICommandInvoker invoker;
public SomeClass(ICommandInvoker invoker)
{
this.invoker = invoker;
}
public void DoSomething()
{
var command = new ChangeNameCommand(1, "asdf");
invoker.Invoke(command);
}
}
The invocation of the command implies "do this command on a unit of work." This is what we want to happen when we invoke the command:
IHandle<ChangeNameCommand>
from the IoC scopeSo here's an example using Autofac as the IoC container:
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.
}
}
Note: The UnitOfWorkInvoker
I've shown here is violating SRP - it is a UnitOfWorkFactory
, a UnitOfWork
, and an Invoker
all in one. In my actual implementation, I broke them out.
When I use repositories, they are contained within a unit of work. The unit of work tracks changes to the repositories and handles transaction management.
Why would it be valid to use a unit of work to handle transaction management in a web application and not in a windows application? If it's an N-Tier application, your business layer would actually be shared between both.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With