Generic Repository and Transaction
asp.net-mvc-3, c#, repository, transactions
Solution
You are missing an abstraction here. You should place all your business logic inside command handlers and create a command handler decorator that implements transaction behavior. This article describes how to do this, but in short:
Define an `ICommandHandler<TCommand>` interface:
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
Create commands that define the contract of a business operation. Commands are simply DTOs (with only data and no behavior). For instance:
public class ShipOrderCommand
{
public int OrderId { get; set; }
public ShippingInfo Info { get; set; }
}
Implement command handlers that will contain the business logic / behavior for those commands:
public class ShipOrderCommandHandler
: ICommandHandler<ShipOrderCommand>
{
private readonly IRepository<Order> repository;
public ShipOrderCommandHandler(
IRepository<Order> repository)
{
this.repository = repository;
}
public void Handle(ShipOrderCommand command)
{
// do some useful stuf with the command and repository.
}
}
Let your MVC Controllers depend on the `ICommandHandler<T>` abstraction:
public ShipOrderController : Controller
{
private readonly ICommandHandler<ShipOrderCommand> handler;
public ShipOrderController(
ICommandHandler<ShipOrderCommand> handler)
{
this.handler = handler;
}
public void Ship(int orderId, ShippingInfo info)
{
this.handler.Handle(new ShipOrderCommand
{
OrderId = orderId,
Info = info
});
}
}
Define a generic decorator that implements transaction logic:
public TransactionalCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
private ICommandHandler<TCommand> decoratedHandler;
public TransactionalCommandHandlerDecorator(
ICommandHandler<TCommand> decoratedHandler)
{
this.decoratedHandler = decoratedHandler;
}
public void Handle(TCommand command)
{
using (var scope = new TransactionScope())
{
this.decoratedHandler.Handle(command);
scope.Complete();
}
}
}
Ensure that each `ShipOrderCommandHandler` is decorated with a `TransactionalCommandHandlerDecorator` and injected into `ShipOrderController`. You can do this with your favorite DI container, or by hand:
protected override IController GetControllerInstance(
RequestContext requestContext, Type controllerType)
{
if (controllerType == typeof(ShipOrderController))
{
return new ShipOrderController(
new TransactionalCommandHandlerDecorator<ShipOrderCommand>(
new ShipOrderCommandHandler(
new OrderRepository())));
}
return base.GetControllerInstance(requestContext, controllerType);
}
With this in place you can run all your business logic inside a transaction, without the need for the business logic to be aware of that.
Problem
I have implemented my first Generic repository in MVC app. Works fine but how to put repositories in Transaction scope? ``` public interface IRepository<TEntity> where TEntity : class { List<TEntity> FetchAll(); IQueryable<TEntity> Query { get; } void Add(TEntity entity); void Delete(TEntity entity); void Save(); } public class Repository<T> : IRepository<T> where T : class { private readonly DataContext _db; public Repository(DataContext db) { _db = db; } #region IRepository<T> Members public IQueryable<T> Query { get { return _db.GetTable<T>(); } } public List<T> FetchAll() { return Query.ToList(); } public void Add(T entity) { _db.GetTable<T>().InsertOnSubmit(entity); } public void Delete(T entity) { _db.GetTable<T>().DeleteOnSubmit(entity); } public void Save() { _db.SubmitChanges(); } #endregion } private void RegisterDependencyResolver() { var kernel = new StandardKernel(); var connectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString; kernel.Bind(typeof(DataContext)).ToMethod(context => new DataContext(connectionString)); kernel.Bind(typeof(IRepository<>)).To(typeof(Repository<>)); DependencyResolver.SetResolver(new NinjectDependencyResolver(kernel)); } public class AdminController : Controller { private readonly IRepository<User> _userRepository; private readonly IRepository<Order> _orderRepository; public AdminController(IRepository<User> userRepository, IRepository<Order> orderRepository) { _userRepository = userRepository; _orderRepository = orderRepository; } public ActionResult InsertUser(UserViewModel model) { //Skip Code //Do not commit data to database if _orderRepository is failed to save data _userRepository.Add(user); _userRepository.Save(); //Skip Code _orderRepository.Add(order); _orderRepository.Save(); } } ``` What would be best method to wrap repository code with Transaction scope in InsertUser action?