如何查看DbContext是否有事务?
How to check whether DbContext has transaction?
背景:
我有 WCF 服务和 SimpleInjector 作为 IoC,它根据 WCF 请求创建 DbContext 实例。
后端本身就是CQRS。 CommandHandlers 有很多装饰器(验证、授权、日志记录、不同处理程序组的一些通用规则等),其中之一是事务装饰器:
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 实例已经有一个事务。
有没有办法检查当前交易是否存在?
我认为您正在寻找 DbContext 的 CurrentTransaction
属性:
var transaction = db.Database.CurrentTransaction;
然后你可以这样检查:
using(var transaction = db.Database.CurrentTransaction ?? db.Database.BeginTransaction())
{
...
}
但是,如果并发方法正在使用事务,我不确定您如何知道何时提交事务。
而不是使用来自 Entity Framework 的 DbContext 的事务,您可以或者应该使用 TransactionScope class,它创建一个环境事务范围并管理所有连接的事务幕后的 (SQL) 数据库。
如果您为 SqlCommand
使用准确的 (case-sensitive) 连接字符串,它甚至会在同一个事务中放置一个直接的 SqlCommand
。写入 MessageQueue 的消息也封装在同一事务中
它甚至可以同时管理与不同数据库的连接。为此,它使用 DTC windows 服务。请注意,如果需要,配置起来会很痛苦。通常,使用单个数据库连接(或到同一数据库的多个连接)您不需要 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
lifestyle is not available. You would need a LifeTimeScope
lifestyle for you commandhandlers now. Because your design allows it, which is great by the way!, you would typicaly wrap your commandhandlers in a 来启动 LifetimeScope
。在您当前的设计中,单个命令处理程序引用其他命令处理程序,您将 运行 遇到问题,因为每个命令处理程序都将在其自己的范围内创建,因此会注入其他 DbContext 而不是其他命令处理程序!
因此您需要重新设计并制作命令处理程序 holistic abstractions 并创建较低级别的抽象来执行 DbContext 操作。
背景: 我有 WCF 服务和 SimpleInjector 作为 IoC,它根据 WCF 请求创建 DbContext 实例。
后端本身就是CQRS。 CommandHandlers 有很多装饰器(验证、授权、日志记录、不同处理程序组的一些通用规则等),其中之一是事务装饰器:
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 实例已经有一个事务。
有没有办法检查当前交易是否存在?
我认为您正在寻找 DbContext 的 CurrentTransaction
属性:
var transaction = db.Database.CurrentTransaction;
然后你可以这样检查:
using(var transaction = db.Database.CurrentTransaction ?? db.Database.BeginTransaction())
{
...
}
但是,如果并发方法正在使用事务,我不确定您如何知道何时提交事务。
而不是使用来自 Entity Framework 的 DbContext 的事务,您可以或者应该使用 TransactionScope class,它创建一个环境事务范围并管理所有连接的事务幕后的 (SQL) 数据库。
如果您为 SqlCommand
使用准确的 (case-sensitive) 连接字符串,它甚至会在同一个事务中放置一个直接的 SqlCommand
。写入 MessageQueue 的消息也封装在同一事务中
它甚至可以同时管理与不同数据库的连接。为此,它使用 DTC windows 服务。请注意,如果需要,配置起来会很痛苦。通常,使用单个数据库连接(或到同一数据库的多个连接)您不需要 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
lifestyle is not available. You would need a LifeTimeScope
lifestyle for you commandhandlers now. Because your design allows it, which is great by the way!, you would typicaly wrap your commandhandlers in a LifetimeScope
。在您当前的设计中,单个命令处理程序引用其他命令处理程序,您将 运行 遇到问题,因为每个命令处理程序都将在其自己的范围内创建,因此会注入其他 DbContext 而不是其他命令处理程序!
因此您需要重新设计并制作命令处理程序 holistic abstractions 并创建较低级别的抽象来执行 DbContext 操作。