2011-07-26 7 views
8

Webアプリケーションでは、トランザクション管理の作業単位は作業単位です。Nhibernate:非Webアプリケーションのトランザクション管理を担当するのは誰ですか?

しかし、Windowsアプリケーションはどうですか?

私の知る限り、リポジトリは自分のデータアクセス層と私のビジネス層の間のコネクタです。 私のビジネスレイヤーからすべてのデータアクセス項目を非表示にします。

この事実を利用して、すべてのトランザクションをリポジトリに取り込むことを考えてみましょう。

しかし、リポジトリでCommit/RollBackメソッドを使用していると、リポジトリの意図に違反しています。

非Webアプリケーションでトランザクション管理を担当するのは誰か、ビジネスレイヤからトランザクション/ Nhibernateを隠すにはどうすればよいですか?

+3

Ayendeには、アプリケーションのhttps://github.com/ayende/Effectus –

+0

があります。これは勝利フォームのためのソロソートかもしれません... Windowsサービスはどうですか? – Rookian

+0

私は成功したパターンを記述することができます。これは、単純な「1トランザクションあたりのトランザクション」モデル(ネストされたトランザクションは不要)とIoCコンテナを使用している場合に適用されます。これはあなたのニーズを満たすでしょうか?基本的には、サービス層コードで「ドメイン作業を行う」と判断された場合、コマンドパターンとその呼び出し元(IoCによってサービスコードに呼び出されます) –

答えて

4

一般的な答えは、「ISessionをインスタンス化する人は誰でも処理してください。トランザクションがコミットされていない場合は、これは事実上ロールバックです」

私は、コマンドパターンを使用して、作業単位で実行したい操作を定義することで成功しました。 Personエンティティがあり、私たちができることの1つは、人の名前を変更することです。

public class ChangeNameCommandHandler : IHandle<ChangeNameCommand> 
{ 
    ISession session; 

    public ChangeNameCommandHandler(ISession session) 
    { 
     // You could demand an IPersonRepository instead of using the session directly. 
     this.session = session; 
    } 

    public void Handle(ChangeNameCommand command) 
    { 
     var person = session.Load<Person>(command.PersonId); 
     person.ChangeName(command.NewName); 
    } 
} 

は目標がそのコードです:

public class Person 
{ 
    public virtual int Id { get; private set; } 
    public virtual string Name { get; private set; } 

    public virtual void ChangeName(string newName) 
    { 
     if (string.IsNullOrWhiteSpace(newName)) 
     { 
      throw new DomainException("Name cannot be empty"); 
     } 

     if (newName.Length > 20) 
     { 
      throw new DomainException("Name cannot exceed 20 characters"); 
     } 

     this.Name = newName; 
    } 
} 

はこのように簡単なPOCOコマンドを定義します:

public class ChangeNameCommand : IDomainCommand 
{ 
    public ChangeNameCommand(int personId, string newName) 
    { 
     this.PersonId = personId; 
     this.NewName = newName; 
    } 

    public int PersonId { get; set; } 
    public string NewName { get; set; } 
} 

...とコマンドのハンドラーのエンティティから始めましょうセッション/作業スコープの外に存在するものは、次のようなことができます。

public class SomeClass 
{ 
    ICommandInvoker invoker; 

    public SomeClass(ICommandInvoker invoker) 
    { 
     this.invoker = invoker; 
    } 

    public void DoSomething() 
    { 
     var command = new ChangeNameCommand(1, "asdf"); 
     invoker.Invoke(command); 
    } 
} 

このコマンドを呼び出すと、「作業ユニット上でこのコマンドを実行します。「これは、我々は、コマンドを呼び出すときに我々が起こることを望むものである:

  1. (IOCネストされたスコープを開始し、 『ワーク』スコープの単位)を
  2. スタートISessionとトランザクション(これはおそらくの一部として暗示されますステップ3)
  3. のIoC範囲からIHandle<ChangeNameCommand>
  4. がハンドラにコマンドを渡し解決(ドメインがその作業を行う)
  5. トランザクションをコミットするのIoCスコープ(作業単位)
  6. を終了

は、だからここIoCコンテナとしてAutofacを使用した例です:

public class UnitOfWorkInvoker : ICommandInvoker 
{ 
    Autofac.ILifetimeScope scope; 

    public UnitOfWorkInvoker(Autofac.ILifetimeScope scope) 
    { 
     this.scope = scope; 
    } 

    public void Invoke<TCommand>(TCommand command) where TCommand : IDomainCommand 
    { 
     using (var workScope = scope.BeginLifetimeScope("UnitOfWork")) // step 1 
     { 
      var handler = workScope.Resolve<IHandle<TCommand>>(); // step 3 (implies step 2) 
      handler.Handle(command); // step 4 

      var session = workScope.Resolve<NHibernate.ISession>(); 
      session.Transaction.Commit(); // step 5 

     } // step 6 - When the "workScope" is disposed, Autofac will dispose the ISession. 
      // If an exception was thrown before the commit, the transaction is rolled back. 
    } 
} 

注:私はここに示されてきたUnitOfWorkInvokerSRPに違反している - それはUnitOfWorkFactoryUnitOfWork、およびオールインワンInvokerです。私の実際の実装では、私はそれらを壊しました。

+0

誰がsession.SaveOrUpdateを呼び出したのですか? IDを割り当てた場合、session.Saveまたはsession.Updateを呼び出す責任は誰にありますか?また、各コマンドのトランザクションをコミットするのが最善かどうかもわかりません。 Webアプリケーションでは、リクエストごとに単一のセッションとトランザクション(通常は遅延ロード/開始)を行い、エラーが発生した場合はリクエストの最後またはロールバックをコミットするのが一般的です。トランザクションはセッションを作成した人が開始するので、コミットまたはロールバックする責任もあります。間違っていますか? – Loudenvier

+0

@Loudenvier - アプリケーションによっては、 "コマンドごとに1つのトランザクション"パターンが適切でないかもしれません。コマンドは、トランザクション境界を描写するための1つの方法に過ぎません。私の例では、UnitOfWorkInvokerは 'Resolve'を呼び出すことによってセッションとトランザクションを暗黙的に作成するので、それをコミットすることができます。また、遅延容器に 'Lazy 'と尋ねることで、怠惰を簡単に追加することができます(Autofacを仮定)。 –

+0

私はあまりにもLazy を使用していますが、より簡単なアプローチでは、Webアプリケーションに合わせて作られています。私はそれが自分のものによく似ていると信じていますが、私はそれを今必要としないので洗練されていません。 私が実際にサンプルで理解できなかったのは、SaveまたはSaveOrUpdateが呼び出される場所でした。 ChangeNameCommandHandlerはperson.ChangeName()を呼び出すだけですが、Save、UpdateまたはSaveOrUpdateは行いません。だから私はあなたが変更された/作成されたエンティティを追跡し、セーブを呼び出すのに不思議に思っていました...おそらくコマンド自体はそれを行うべきでしょうか? – Loudenvier

1

リポジトリを使用する場合、それらは作業単位内に含まれます。作業単位は、リポジトリーの変更を追跡し、トランザクション管理を処理します。

Windowsアプリケーションではなく、Webアプリケーションでトランザクション管理を処理するために作業ユニットを使用することが有効なのはなぜですか? N層アプリケーションの場合、実際には両方のビジネス層が共有されます。

+0

私はここでジョエルに同意します。なぜ違うのか分かりません。現在私はリポジトリを使用しており、IUnitOfWork \ ISessionを各リポジトリに渡します。 –

+0

IUnitOfWorkをリポジトリに追加する – Rookian

+0

リポジトリーと作業単位は、ISession/IStatelessSessionに依存します。 Webの両方でHttpContext内のセッションを共有します。非ウェブアプリケーションでは、あなた自身の辞書にセッションを入れなければなりません。はい、今私はうそをついた... – Rookian

関連する問題