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?

Original source

Related problems