31

如何使用存储库模式以事务方式封装保存多个实体?例如,如果我想添加订单并根据订单创建更新客户状态,但只有在订单成功完成时才会这样做。请记住,在本例中,订单不是客户内部的集合。他们是他们自己的实体。存储库模式中的事务

这只是一个人为的例子,所以我并不关心订单是否应该在客户对象内部,或者甚至在相同的有界环境中。我并不在乎底层技术的用途(nHibernate,EF,ADO.Net,Linq等)。我只是想看看在这个公认的人为操作的例子中,一些调用代码可能看起来像什么样子。

回答

7

我会看看使用某种类型的事务范围/上下文系统。所以你可能有下面的代码,它大致基于.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。如果您使用TransactionScope,那么现在在.net中,您有可能会对DTC产生影响,但将来会解决这个问题。

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

+0

我不认为这是DDD精神的解决方案。基本上你已经创建了一个交易脚本来完成域模型的工作。例如,服务不应改变客户状态。 – 2013-10-30 09:06:36

+1

代码中的某些东西必须处理这个业务规则,无论是在这个级别还是更高级别上,在单个TransactionScope中进行更改都允许本地事务或分布式事务处理事务。如果业务规则规定每当下订单时都要更新客户,那么这是一个处理所有订单的好地方。 – JoshBerke 2013-11-02 18:12:42

3

你想看看实施工作模式的单位。有NHibernate的实现。一个在Rhino Commons项目中,还有Machine.UoW。

5

使用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的文档。

2

如何封装的 节约超过一个实体更多使用 库模式的 事务的方式?例如,如果我想添加订单并根据 订单创建客户状态 更新什么 ,但只有在订单成功完成 订单时才这样做?保留在 请注意,对于此示例,订单 不是客户内部的集合。 他们是他们自己的实体。

它不是仓库的责任,它通常是在更高层次上完成的。虽然你说你对特定技术不感兴趣,但我认为它值得追求解决方案,例如在使用NHibernate和Web应用程序时,你可能会考虑使用session-per request

所以,如果你能够在更高层次上的管理事务,然后我的两个选择是:

  1. 前期检查 - 例如,在服务协调行为的决定,如果你想通过要求继续订单/客户,如果他们说他们甚至不尝试更新他们中的任何一个。
  2. 回滚 - 只需继续更新客户/订单,如果事情部分通过回滚数据库事务处理失败。

如果您选择第二个选项,那么问题是内存中的对象会发生什么,您的客户可能会处于不一致的状态。如果这一点很重要,而且我的工作方式并不像对象只是为了请求而加载的那样,那么我会考虑先检查它是否可能,因为它比替代方案更容易(回滚内 - 内存更改或重新加载对象)。

+1

为什么它不是仓库的责任?将数据库操作从领域模型中抽象出来不是整个想法吗?对我而言,存储库是放置该事务支持的最佳地点。 – 2009-02-22 19:31:27

12

今天早上启动我的电脑我遇到了我正在进行的项目的确切问题。我有一些想法导致了以下设计 - 评论将不仅仅是真棒。不幸的是,Josh建议的设计是不可能的,因为我必须使用远程SQL服务器,并且无法启用它所依赖的Distribute Transaction Coordinator服务。

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

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

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

的想法是,在我所有的存储库都实现了这个接口,并添加了直接引入事务的代码,具体取决于实际的提供者(对于伪代码库,我已经创建了一个在提交时执行的代理列表)。对于LINQ to SQL中它会很容易做出的实现,例如:

#region IHasTransactions Members 

public void BeginTransaction() 
{ 
    _db.Transaction = _db.Connection.BeginTransaction(); 
} 

public void CommitTransaction() 
{ 
    _db.Transaction.Commit(); 
} 

#endregion 

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

如果存储库实现了IHasTransactions,则使用存储库的每种方法都需要调用BeginTransaction()EndTransaction()。为了使这个呼叫更容易,我想出了以下扩展:

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

评论感谢!