51

リポジトリ パターンを使用してトランザクション方式で複数のエンティティの保存をカプセル化するにはどうすればよいですか? たとえば、注文を追加し、その注文の作成に基づいて顧客ステータスを更新したいが、注文が正常に完了した場合にのみそうする場合はどうすればよいですか? この例では、注文は顧客内のコレクションではないことに注意してください。それらは独自のエンティティです。

これは単なる不自然な例であるため、注文が顧客オブジェクト内にあるべきかどうか、または同じ境界コンテキスト内にあるべきかどうかはあまり気にしません。基礎となるテクノロジ (nHibernate、EF、ADO.Net、Linq など) が使用されるかどうかはあまり気にしません。この明らかに不自然な操作の全か無かの例で、一部の呼び出しコードがどのように見えるかを確認したいだけです。

4

7 に答える 7

20

今朝コンピュータを起動すると、私が取り組んでいるプロジェクトの正確な問題に直面しました。次のデザインにつながるいくつかのアイデアがありました。残念ながら、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

もちろん、これにはスレッドごとに新しいリポジトリ クラスを作成する必要がありますが、これは私のプロジェクトでは妥当です。

リポジトリが を実装している場合、リポジトリを使用する各メソッドは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();
        }
    }
}

コメントは大歓迎です!

于 2009-02-23T11:51:36.997 に答える
11

ある種のトランザクション スコープ / コンテキスト システムの使用を検討します。したがって、大まかに.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にエスカレーションするリスクがありますが、これは将来解決される予定です。

基本的に DB 接続を管理し、ローカル SQL トランザクションを使用する独自の TransactionScope クラスを作成しました。

于 2009-02-22T16:20:30.463 に答える
6

リポジトリ パターンを使用してトランザクション方式で複数のエンティティの保存をカプセル化するにはどうすればよいですか? たとえば、注文を追加し、その注文の作成に基づいて顧客ステータスを更新したいが、注文が正常に完了した場合にのみそうする場合はどうすればよいですか? この例では、注文は顧客内のコレクションではないことに注意してください。それらは独自のエンティティです。

これはリポジトリの責任ではなく、通常はより高いレベルで行われるものです。あなたは特定の技術に興味がないと言っていましたが、ソリューションを結び付ける価値があると思います.

したがって、より高いレベルでトランザクションを管理できる場合、私の 2 つのオプションは次のようになります。

  1. 事前チェック- たとえば、動作を調整するサービスでは、注文/顧客に尋ねて続行するかどうかを決定します。どちらかがそうしないと言った場合、どちらも更新しようとしません。
  2. ロールバック- 顧客/注文の更新を続行し、途中で失敗した場合はデータベース トランザクションをロールバックします。

2 番目のオプションを選択した場合、問題はインメモリ オブジェクトに何が起こるかということです。Customer は一貫性のない状態のままになる可能性があります。それが問題であり、オブジェクトがその要求に対してのみ読み込まれたため、そうでないシナリオで作業している場合は、代替手段よりもはるかに簡単であるため、可能な場合は事前チェックを検討します (インをロールバックする) -メモリの変更またはオブジェクトの再ロード)。

于 2009-02-22T18:14:30.877 に答える
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 は、CreateOrder メソッドを呼び出したときにトランザクションを適用する CustomerService クラスのプロキシを返します。この方法では、サービス クラス内にトランザクション固有のコードはありません。AOP が処理します。詳細については、Spring.NETのドキュメントをご覧ください。

于 2009-02-22T16:36:16.183 に答える
3

作業単位パターンの実装を検討したいと考えています。NHibernate の実装があります。1 つは Rhino Commons プロジェクトにあり、Machine.UoW もあります。

于 2009-02-22T16:22:53.047 に答える