背景: 我有使用SimpleInjector的WCF服务作为IoC,它为每个WCF请求创建DbContext实例。
后端本身就是CQRS。 CommandHandlers有很多装饰器(验证,授权,日志记录,不同处理程序组的一些通用规则等),其中一个是Transaction Decorator:
public class TransactionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
where TCommand : ICommand
{
private readonly ICommandHandler<TCommand> _handler;
private readonly IMyDbContext _context;
private readonly IPrincipal _principal;
public TransactionCommandHandlerDecorator(ICommandHandler<TCommand> handler,
IMyDbContext context, IPrincipal principal)
{
_handler = handler;
_context = context;
_principal = principal;
}
void ICommandHandler<TCommand>.Handle(TCommand command)
{
using (var transaction = _context.Database.BeginTransaction())
{
try
{
var user = _context.User.Single(x => x.LoginName == _principal.Identity.Name);
_handler.Handle(command);
_context.SaveChangesWithinExplicitTransaction(user);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw;
}
}
}
}
当任何命令尝试链接在同一WCF请求中执行另一个命令时,会出现问题。 我在这一行得到了一个预期的例外:
using (var transaction = _context.Database.BeginTransaction())
因为我的DbContext实例已经有了一个事务。
有没有办法检查当前的交易存在?
答案 0 :(得分:15)
我认为你正在寻找DbContext的CurrentTransaction
属性:
var transaction = db.Database.CurrentTransaction;
然后你可以这样检查一下:
using(var transaction = db.Database.CurrentTransaction ?? db.Database.BeginTransaction())
{
...
}
但是,如果并发方法正在使用该事务,我不知道如何知道何时提交事务。
答案 1 :(得分:5)
您可以或者可以使用创建环境事务范围的TransactionScope类,并管理封面下对(SQL)数据库所做的所有连接的事务,而不是使用实体框架的DbContext中的事务。
如果你要使用SqlCommand
的确切(区分大小写)连接字符串,它甚至会在同一个事务中放置一个直接SqlCommand
。写入MessageQueue的消息也封装在同一事务中
它甚至可以同时管理与不同数据库的连接。它使用DTC Windows服务。请注意,如果需要,这是一个很难配置。通常,使用单个DB连接(或与同一个DB的多个连接),您将不需要DTC。
TransactionScopeCommandHandlerDecorator
实施很简单:
public class TransactionScopeCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
private readonly ICommandHandler<TCommand> decoratee;
public TransactionScopeCommandHandlerDecorator(ICommandHandler<TCommand> decoratee)
{
this.decoratee = decoratee;
}
public void Handle(TCommand command)
{
using (var scope = new TransactionScope())
{
this.decoratee.Handle(command);
scope.Complete();
}
}
}
但是:正如qujck在评论中已经提到的那样,你错过了ICommandHandler
作为原子操作的概念。一个命令处理程序不应该引用另一个命令处理程序。这不仅对交易有害,还会考虑这个:
想象一下,应用程序会增长,你会将一些命令处理程序重构为后台线程,后台线程将在某些Windows服务中运行。在这个Windows服务中,PerWcfOperation
生活方式不可用。你现在需要一个LifeTimeScope
生活方式为你的命令工具。因为你的设计允许它,顺便说一句!,你可以典型地将你的命令处理程序包裹在LifetimeScopeCommandHandler
decorator中以启动LifetimeScope
。在您当前的设计中,单个命令处理程序引用其他命令处理程序,您将遇到问题,因为每个命令处理程序将在其自己的作用域中创建,因此获得注入的其他DbContext比其他命令处理程序!
因此,您需要进行一些重新设计并制作命令处理程序holistic abstractions,并为执行DbContext操作创建一个较低级别的抽象。