2012-04-04 16 views
6

He implementado mi primer repositorio genérico en la aplicación MVC. Funciona bien, pero ¿cómo poner repositorios en el alcance de la transacción?Repositorio genérico y transacción

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(); 

} 


} 

¿Cuál sería el mejor método para ajustar el código del repositorio con el ámbito de la transacción en la acción InsertUser?

+0

También eche un vistazo a [este artículo] (http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=89). – Steven

Respuesta

9

Aquí falta una abstracción. Debe colocar toda su lógica de negocio dentro de los controladores de comando y crear un decorador de controlador de comandos que implemente el comportamiento de la transacción. This article describe cómo hacer esto, pero en resumen:

  1. definir una interfaz ICommandHandler<TCommand>:

    public interface ICommandHandler<TCommand> 
    { 
        void Handle(TCommand command); 
    } 
    
  2. crear comandos que definen el contrato de una operación de negocios. Los comandos son simplemente DTOs (con solo datos y sin comportamiento). Por ejemplo:

    public class ShipOrderCommand 
    { 
        public int OrderId { get; set; } 
    
        public ShippingInfo Info { get; set; } 
    } 
    
  3. Implementar controladores de comandos que van a contener la lógica de negocio/comportamiento de estos comandos:

    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. Deje que sus controladores MVC dependen del ICommandHandler<T> abstracción:

    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. Defina un decorador genérico que implemente la lógica de transacción:

    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. asegurar que cada ShipOrderCommandHandler está decorada con un TransactionalCommandHandlerDecorator y se inyecta en ShipOrderController. Puede hacer esto con su contenedor de DI favorito, o con la mano:

    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); 
    } 
    

Con esto en su lugar se puede ejecutar toda la lógica de negocio dentro de una transacción, sin la necesidad de la lógica de negocio a tener en cuenta ese.

+0

O ... podría manejar el ámbito de transacción en el nivel BeginRequest/EndRequest (ya que esta es una aplicación web, después de todo) y evitar cientos de líneas de abstracciones innecesarias y códigos intrincados. – Chris

+2

@Chris: No estoy de acuerdo con dos puntos. 1. Que es una abstracción innecesaria. Este código se adhiere a los principios SOLIDOS y mantendrá su aplicación comprobable, escalable y mantenible. 2. Que son cientos de líneas de código extra. De hecho, le evitará tener muchas líneas de código duplicado. – Steven

+0

@Steven quizás tenga un ejemplo de código sobre cómo inyectar una nueva instancia de TransactionalCommandHandlerDecorator usando Ninject? – Tomas

1

Hay un patrón llamado Unidad de trabajo. Aquí está an explanation.

+0

+1 UOW es de hecho el patrón aplicable aquí. –

+0

El DBContext * de EntityFramework es * ¡su unidad de trabajo! – Mardoxx

Cuestiones relacionadas