通用仓储库和事务处理

6

我已经在MVC应用程序中实现了我的第一个通用存储库。它运行良好,但如何将存储库放入事务范围内?

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

}


}

在InsertUser操作中,将存储库代码与事务范围一起包装的最佳方法是什么?


请查看这篇文章 - Steven
2个回答

8

您在这里缺少一个抽象层。您应该将所有业务逻辑放在命令处理程序中,并创建一个实现事务行为的命令处理程序装饰器。 本文描述了如何做到这一点,但简而言之:

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

有了这个功能,您可以在一个事务中运行所有业务逻辑,而不需要业务逻辑知道它正在进行。


或者...你可以在BeginRequest/EndRequest级别处理事务范围(毕竟这是一个Web应用程序),避免数百行不必要的抽象和复杂代码。 - Chris
2
@Chris:我不同意两点。1. 这是必要的抽象。这段代码遵循SOLID原则,可以使您的应用程序易于测试、可扩展和可维护。2. 它并不会增加数百行额外的代码。事实上,它将帮助您避免许多重复代码的出现。 - Steven
这个SO问题可能会有所帮助:https://dev59.com/Gl7Va4cB1Zd3GeqPGyFQ。 - Steven
然而,我不确定如何使用Ninject注册通用装饰器,但我认为这是可能的。当使用Simple Injector时,您只需执行以下操作:container.RegisterGenericDecorator(typeof(ICommandHandler<>), typeof(TransactionCommandHandlerDecorator<>)); 就完成了。 - Steven
@Steve 我尝试使用 Ninject 进行绑定 kernel.Bind(typeof(ICommandHandler<>)).To(typeof(TransactionCommandHandlerDecorator<>)),但是出现了错误:Error activating ICommandHandler{InsertUserCommand} using binding from ICommandHandler{TCommand} to TransactionCommandHandlerDecorator{TCommand} 检测到两个服务的构造函数之间存在循环依赖。我正在使用 Ninject 绑定仓库,一切都正常 kernel.Bind(typeof(IRepository<>)).To(typeof(Repository<>))。 - Tomas
显示剩余6条评论

1

有一种叫做工作单元的模式。这里是一个解释


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接