このような構成を使用しないことを強くお勧めします。いくつかの理由で:
- データコンテキストをコミットするのは、コントローラー(またはコントローラーで装飾された属性)の責任ではありません。
- これにより、多くの重複コードが発生します(この属性を使用して多くのメソッドを装飾する必要があります)。
- (メソッド内の)実行のその時点で
OnActionExecuted
、データをコミットすることが実際に安全かどうか。
特に3番目のポイントはあなたの注意を引くべきでした。モデルが有効であるという単なる事実は、データコンテキストの変更を送信しても問題がないことを意味するものではありません。この例を見てください:
[UnitOfWorkAttribute]
public View MoveCustomer(int customerId, Address address)
{
try
{
this.customerService.MoveCustomer(customerId, address);
}
catch { }
return View();
}
もちろん、この例は少しナイーブです。すべての例外を飲み込むことはほとんどありません。それはまったく間違っているでしょう。しかし、それが示しているのは、データを保存してはならないときに、アクションメソッドが正常に終了する可能性が非常に高いことです。
しかし、これに加えて、トランザクションをコミットすることは本当にMVCの問題であり、そうだと判断した場合でも、すべてのアクションメソッドをこの属性で装飾する必要があります。コントローラーレベルで何もしなくても、これを実装するだけでいいのではないでしょうか。なぜなら、この後にどの属性を追加するのですか?承認属性?ロギング属性?属性をトレースしますか?どこで止まりますか?
代わりに試すことができるのは、トランザクションで実行する必要のあるすべてのビジネスオペレーションをモデル化することです。これにより、既存のコードを変更したり、新しい属性をあちこちに追加したりすることなく、この動作を動的に追加できます。これを行う方法は、これらのビジネスオペレーションのインターフェイスを定義することです。例えば:
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
このインターフェイスを使用すると、コントローラーは次のようになります。
private readonly ICommandHandler<MoveCustomerCommand> handler;
// constructor
public CustomerController(
ICommandHandler<MoveCustomerCommand> handler)
{
this.handler = handler;
}
public View MoveCustomer(int customerId, Address address)
{
var command = new MoveCustomerCommand
{
CustomerId = customerId,
Address = address,
};
this.handler.Handle(command);
return View();
}
システム内のビジネスオペレーションごとに、クラス(DTOおよびパラメータオブジェクト)を定義します。例では、MoveCustomerCommand
クラス。このクラスにはデータのみが含まれます。実装は、の実装であるクラスで定義されICommandHandler<MoveCustomerCommand>
ます。例えば:
public class MoveCustomerCommandHandler
: ICommandHandler<MoveCustomerCommand>
{
private readonly IDataContext context;
public MoveCustomerCommandHandler(IDataContext context)
{
this.context = context;
}
public void Handle(MoveCustomerCommand command)
{
// TODO: Put logic here.
}
}
これは非常に多くの余分な役に立たないコードのように見えますが、これは実際には本当に便利です(そしてよく見ると、とにかくそれほど多くの余分なコードではありません)。
これについて興味深いのは、システム内のすべてのコマンドハンドラーのトランザクションを処理する単一のデコレーターを定義できることです。
public class TransactionalCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
private readonly IDataContext context;
private readonly ICommandHandler<TCommand> decoratedHandler;
public TransactionalCommandHandlerDecorator(IDataContext context,
ICommandHandler<TCommand> decoratedHandler)
{
this.context = context;
this.decoratedHandler = decoratedHandler;
}
public void Handle(TCommand command)
{
this.decoratedHandler.Handle(command);
this.context.SubmitChanges();
}
}
これはあなたのコードよりもはるかに多くのコードではありませんUnitOfWorkAttribute
が、違いは、コントローラーがこれを知らなくても、このハンドラーを任意の実装にラップして任意のコントローラーに挿入できることです。そして、コマンドを実行した直後は、変更を保存できるかどうかを実際に知ることができる唯一の安全な場所です。
アプリケーションを設計するこの方法の詳細については、この記事を参照してください。一方...私のアーキテクチャのコマンド側