我强烈建议不要使用这样的构造。有几个原因:
- 提交数据上下文不是控制器(或控制器装饰属性)的责任。
- 这会导致大量重复的代码(你必须用这个属性装饰很多方法)。
- 在执行(在
OnActionExecuted
方法中)的那个时刻,提交数据是否实际上是安全的。
尤其是第三点应该引起你的注意。模型有效这一事实并不意味着可以提交数据上下文的更改。看这个例子:
[UnitOfWorkAttribute]
public View MoveCustomer(int customerId, Address address)
{
try
{
this.customerService.MoveCustomer(customerId, address);
}
catch { }
return View();
}
当然这个例子有点幼稚。你几乎不会吞下每一个例外,那是完全错误的。但它确实表明,当不应该保存数据时,操作方法很有可能成功完成。
但除此之外,提交事务真的是 MVC 的一个问题,如果你确定它是,你是否还想用这个属性装饰所有的动作方法。如果您无需在 Controller 级别执行任何操作就实现这一点,会不会更好?因为,在此之后您要添加哪些属性?授权属性?记录属性?追踪属性?它停在哪里?
相反,您可以尝试对需要在事务中运行的所有业务操作进行建模,以一种允许您动态添加此行为的方式进行建模,而无需更改任何现有代码或在各处添加新属性。一种方法是为这些业务操作定义一个接口。例如:
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
,但不同的是,这个处理程序可以包装任何实现并注入任何控制器,而控制器不需要知道这一点。在执行命令后直接是真正知道是否可以保存更改的唯一安全位置。
您可以在本文中找到有关这种设计应用程序的方式的更多信息:同时...在我的架构的命令端