我会强烈反对忠告使用这样的结构。由于几个原因:
- 它不是控制器(或在控制器装饰属性上)提交数据上下文的责任。
- 这会导致大量重复的代码(您将不得不使用该属性修饰大量方法)。
- 同时在执行(在
OnActionExecuted
法)该点是否是实际安全提交数据。
特别是第三点,应该已经引起你的注意。模型是有效的这一事实并不意味着提交数据上下文的变化是可以的。看看这个例子:
[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和Parameter Object)。在例子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
,但不同的是,这个处理程序可以被包装在任何实现中并注入任何控制器,而无需控制器知道这一点。执行命令后直接执行是真正的唯一安全的地方,您可以真正知道是否可以保存更改。
您可以找到有关这篇文章中设计应用程序的这种方式的更多信息:Meanwhile... on the command side of my architecture
很好的答案,博客文章,这需要重写整个应用程序,所以它可能不会在目前的答案,但它提供了很好的食物的想法。 – Giedrius
对于我而言,如果没有看到您的应用程序代码,我很难说这些,但在大多数情况下,添加此设计并不难。您可以逐步介绍它,并通过让处理程序简单地传递数据开始。然而,这可能是一个很大的转变。 – Steven
也google搜索“不使用CQRS”给出了很多的想法也一样,所以我想我需要自己试试吧,看看它如何适应。 – Giedrius