代码之家  ›  专栏  ›  技术社区  ›  Charles Graham

存储库模式中的事务

  •  35
  • Charles Graham  · 技术社区  · 15 年前

    如何使用存储库模式以事务方式封装多个实体的保存?例如,如果我想添加一个订单并基于该订单创建更新客户状态,但仅当订单成功完成时才执行此操作,该怎么办?请记住,在本例中,订单不是客户内部的集合。它们是自己的实体。

    这只是一个虚构的例子,所以我并不关心订单是否应该或不应该在客户对象内,或者甚至在相同的有界上下文中。我真的不在乎将使用什么底层技术(nhibernate、ef、ado.net、linq等),我只想看看在这个公认的全有或全无操作的例子中,一些调用代码可能是什么样子。

    5 回复  |  直到 15 年前
        1
  •  14
  •   Troels Thomsen    15 年前

    今天早上我启动电脑时遇到了一个我正在做的项目的问题。我有一些想法,导致下面的设计-评论将是可怕的。不幸的是,josh建议的设计是不可能的,因为我必须使用远程sql服务器,并且无法启用它所依赖的分布式事务协调器服务。

    我的解决方案基于对现有代码的一些简单更改。

    首先,我的所有库都实现了一个简单的标记接口:

    /// <summary>
    /// A base interface for all repositories to implement.
    /// </summary>
    public interface IRepository
    { }
    

    其次,我让所有支持事务的存储库实现以下接口:

    /// <summary>
    /// Provides methods to enable transaction support.
    /// </summary>
    public interface IHasTransactions : IRepository
    {
        /// <summary>
        /// Initiates a transaction scope.
        /// </summary>
        void BeginTransaction();
    
        /// <summary>
        /// Executes the transaction.
        /// </summary>
        void CommitTransaction();
    }
    

    这个想法是,在我的所有存储库中,我实现了这个接口并添加了代码,这些代码直接依赖于实际的提供者引入事务(对于假存储库,我已经在提交上执行了一个委托列表)。对于LINQtoSQL,很容易实现诸如:

    #region IHasTransactions Members
    
    public void BeginTransaction()
    {
        _db.Transaction = _db.Connection.BeginTransaction();
    }
    
    public void CommitTransaction()
    {
        _db.Transaction.Commit();
    }
    
    #endregion
    

    当然,这需要为每个线程创建一个新的存储库类,但这对于我的项目来说是合理的。

    使用存储库的每个方法都需要调用 BeginTransaction() 以及 EndTransaction() ,如果存储库实现 IHasTransactions 是的。为了使这个电话更简单,我提出了以下扩展:

    /// <summary>
    /// Extensions for spawning and subsequently executing a transaction.
    /// </summary>
    public static class TransactionExtensions
    {
        /// <summary>
        /// Begins a transaction if the repository implements <see cref="IHasTransactions"/>.
        /// </summary>
        /// <param name="repository"></param>
        public static void BeginTransaction(this IRepository repository)
        {
            var transactionSupport = repository as IHasTransactions;
            if (transactionSupport != null)
            {
                transactionSupport.BeginTransaction();
            }
        }
    
        public static void CommitTransaction(this IRepository repository)
        {
            var transactionSupport = repository as IHasTransactions;
            if (transactionSupport != null)
            {
                transactionSupport.CommitTransaction();
            }
        }
    }
    

    欢迎评论!

        2
  •  8
  •   JoshBerke    15 年前

    我将研究使用某种类型的事务作用域/上下文系统。因此,您可能拥有以下代码,这些代码大致基于.NET&C。

    public class OrderService
    {
    
    public void CreateNewOrder(Order order, Customer customer)
    {
      //Set up our transactional boundary.
      using (TransactionScope ts=new TransactionScope())
      {
        IOrderRepository orderRepos=GetOrderRespository();
        orderRepos.SaveNew(order);
        customer.Status=CustomerStatus.OrderPlaced;
    
        ICustomerRepository customerRepository=GetCustomerRepository();
        customerRepository.Save(customer)
        ts.Commit();   
       }
    }
    }
    

    TransactionScope可以嵌套,因此假设您有一个跨越多个服务的操作,您的应用程序也将创建一个TransactionScope。现在,在当前的.NET中,如果您使用TransactionScope,它们会使您有可能降级为一个DTC,但这将在将来得到解决。

    我们创建了自己的transactionscope类,该类基本上管理数据库连接并使用本地sql事务。

        3
  •  5
  •   Darin Dimitrov    15 年前

    使用spring.net aop+nhibernate,您可以正常编写存储库类,并在自定义xml文件中配置事务:

    public class CustomerService : ICustomerService
    {
        private readonly ICustomerRepository _customerRepository;
        private readonly IOrderRepository _orderRepository;
    
        public CustomerService(
            ICustomerRepository customerRepository, 
            IOrderRepository orderRepository) 
        {
            _customerRepository = customerRepository;
            _orderRepository = orderRepository;
        }
    
        public int CreateOrder(Order o, Customer c) 
        {
            // Do something with _customerRepository and _orderRepository
        }
    }
    

    在XML文件中,选择要在事务中执行的方法:

      <object id="TxProxyConfigurationTemplate" 
              abstract="true"
              type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data">
    
        <property name="PlatformTransactionManager" ref="HibernateTransactionManager"/>
    
        <property name="TransactionAttributes">
          <name-values>
            <add key="Create*" value="PROPAGATION_REQUIRED"/>
          </name-values>
        </property>
      </object>
    
      <object id="customerService" parent="TxProxyConfigurationTemplate">
        <property name="Target">
          <object type="MyNamespace.CustomerService, HibernateTest">
              <constructor-arg name="customerRepository" ref="customerRepository" />
              <constructor-arg name="orderRepository" ref="orderRepository" />
          </object>
        </property>
    
      </object>
    

    在代码中,您将获得customerservice类的一个实例,如下所示:

    ICustomerService customerService = (ICustomerService)ContextRegistry
        .GetContent()
        .GetObject("customerService");
    

    spring.net将返回一个customerservice类的代理,当您调用createOrder方法时,该代理将应用一个事务。这样,在服务类中就没有特定于事务的代码。AOP负责。有关更多细节,您可以查看文档。 Spring.NET 是的。

        4
  •  3
  •   Garry Shutler    15 年前

    您希望看到工作单元模式的实现。在那里有NHiBiNATE的实现。一个是在rhino commons项目中,还有machine.uow。

        5
  •  3
  •   Colin Jack    15 年前

    如何封装 中的多个实体 使用 存储库模式?例如,什么 如果我想添加订单并更新 基于此的客户状态 创建订单,但前提是 订单成功完成?坚持住 注意在这个例子中,命令是 不是客户内部的收藏。 它们是自己的实体。

    这不是存储库的责任,通常是在更高的层次上完成的事情。尽管你说你对特定的技术不感兴趣,但我认为把这些解决方案捆绑在一起是值得的,例如,当你将nhibernate与一个你可能会考虑使用的web应用程序一起使用时 session-per request 是的。

    因此,如果您可以在更高的级别管理事务,那么我的两个选择是:

    1. 前期检查 例如,在一个服务中,协调行为,决定是否要通过询问订单/客户来进行,如果他们说没有,那么就不要尝试更新其中的任何一个。
    2. 回降 -只是继续更新客户/订单,如果事情失败,部分通过回滚数据库事务。

    如果你选择第二个选项,那么问题是内存中的对象会发生什么,你的客户可能会处于不一致的状态。如果这很重要,而且我工作的情况下,它并不是因为该对象只加载了该请求,那么我会考虑前面的检查是否可能,因为它比其他选项更容易(回滚内存中的更改或重新加载对象)。

        6
  •  1
  •   Ian Warburton    5 年前

    可以将事务参数添加到要在事务中运行的方法的末尾,并给它提供默认值NULL。因此,如果不想在现有事务中运行该方法,则将结束参数或显式传递NULL。

    在这些方法中,可以检查NULL参数以确定是否创建新事务或使用传入的事务。例如,可以将此逻辑推送到基类。

    这使您的方法比使用基于上下文的解决方案更纯净,尽管后者对于通用库可能更好。不过,在一个独立的应用程序中,你知道哪些方法需要在事务中被链接起来,而不是全部。

    void Update(int itemId, string text, IDbTransaction trans = null) =>
       RunInTransaction(ref trans, () =>
       {
          trans.Connection.Update("...");
       });
    
    void RunInTransaction(ref IDbTransaction transaction, Action f)
    {
        if (transaction == null)
        {
            using (var conn = DatabaseConnectionFactory.Create())
            {
                conn.Open();
    
                using (transaction = conn.BeginTransaction())
                {
                    f();
    
                    transaction.Commit();
                }
            }
        }
        else
        {
            f();
        }
    }
    
    Update(1, "Hello World!");
    Update(1, "Hello World!", transaction);
    

    然后你可以为你的服务层有一个事务管理器…

    public void RunInTransaction(Action<IDbTransaction> f)
    {
       using (var conn = DatabaseConnectionFactory.Create())
       {
          conn.Open();
    
          using (var transaction = conn.BeginTransaction())
          {
             f(transaction);
    
             transaction.Commit();
          }
       }
    }
    

    服务方法可能是这样的…

    void MyServiceMethod(int itemId, string text1, string text2) =>
       transactionRunner.RunInTransaction(trans =>
       {
          repos.UpdateSomething(itemId, text1, trans);
          repos.UpdateSomethingElse(itemId, text2, trans);
       });
    

    这很容易模拟单元测试…

    public class MockTransactionRunner : ITransactionRunner
    {
       public void RunInTransaction(Action<IDbTransaction> f) => f(null);
    }
    
    推荐文章