6

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?

Tomas
  • 17,551
  • 43
  • 152
  • 257
  • Also take a look at [this article](http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=89). – Steven Apr 04 '12 at 14:10

2 Answers2

8

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:

  1. Define an ICommandHandler<TCommand> interface:

    public interface ICommandHandler<TCommand>
    {
        void Handle(TCommand command);
    }
    
  2. 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; }
    }
    
  3. 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.
        }
    }
    
  4. 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
            });
        }
    }
    
  5. 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();
            }
        }
    }
    
  6. 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.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • Or... you could handle transaction scope at the BeginRequest/EndRequest level (since this is a web app, after all), and avoid hundreds of lines of unnecessary abstractions and convoluted code. – Chris Apr 04 '12 at 13:33
  • 2
    @Chris: I disagree with two points. 1. That it is unnecessary abstraction. This code adheres to the SOLID principles and will keep your application testable, scalable, and maintainable. 2. That it is hundreds of lines of extra code. In fact, it will save you from having many lines of duplicate code. – Steven Apr 04 '12 at 13:42
  • @Steven maybe you have code example how to inject new instance of TransactionalCommandHandlerDecorator using Ninject? – Tomas Apr 04 '12 at 14:03
  • This SO question may be of some help: http://stackoverflow.com/questions/8447037/how-the-binding-are-done-with-decorators-using-ninject. – Steven Apr 04 '12 at 14:06
  • However, I'm not sure how register generic decorators with Ninject, but I think it is possible. When using Simple Injector, you can simply do: `container.RegisterGenericDecorator(typeof(ICommandHandler<>), typeof(TransactionCommandHandlerDecorator<>));` and you're done. – Steven Apr 04 '12 at 14:07
  • @Chris: Would you be so kind to post an answer that shows how to do this using BeginRequest/EndRequest? This allows the OP to pick the solution that works best for him. – Steven Apr 04 '12 at 14:11
  • @Steve I have tried with Ninject kernel.Bind(typeof(ICommandHandler<>)).To(typeof(TransactionCommandHandlerDecorator<>)) and got error: Error activating ICommandHandler{InsertUserCommand} using binding from ICommandHandler{TCommand} to TransactionCommandHandlerDecorator{TCommand} A cyclical dependency was detected between the constructors of two services. I am suing Ninject to bind repository and everything works fine kernel.Bind(typeof(IRepository<>)).To(typeof(Repository<>)) – Tomas Apr 04 '12 at 14:19
  • @Tomas: I'm sorry, I'm no Ninject expert (only a Simple Injector expert), but if you post a new question here at SO, I'm sure you get an answer quickly. – Steven Apr 04 '12 at 14:23
  • @Steven It seems that Ninject inject dependencies not only in Controller constructor but also in TransactionalCommandHandlerDecorator class constructor which cause exception. I will ask Ninject experts for solution. Thank you for your help! – Tomas Apr 04 '12 at 14:32
  • @Steven I like this approach, however it has been passed a quite long time over this post, would you still use that structure or suggest another way/pattern for transactions in generic repository? – ibubi Oct 12 '17 at 11:10
  • @ibubi I still advise to use this structure because of the enormous benefits that it has. – Steven Oct 12 '17 at 11:26
1

There's a pattern called Unit of work. Here's an explanation.

StuffHappens
  • 6,457
  • 13
  • 70
  • 95