C# 如何检查DbContext是否有事务?
背景: 我有一个WCF服务,SimpleInjector作为IoC,它根据WCF请求创建DbContext实例 后端本身就是CQRS。CommandHandler有很多装饰器(验证、授权、日志记录、不同处理程序组的一些通用规则等),其中一个是事务装饰器:C# 如何检查DbContext是否有事务?,c#,entity-framework,simple-injector,C#,Entity Framework,Simple Injector,背景: 我有一个WCF服务,SimpleInjector作为IoC,它根据WCF请求创建DbContext实例 后端本身就是CQRS。CommandHandler有很多装饰器(验证、授权、日志记录、不同处理程序组的一些通用规则等),其中一个是事务装饰器: public class TransactionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand> where TCommand : IC
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;
}
}
}
}
因为我的DbContext实例已经有一个事务
是否有任何方法可以检查当前事务是否存在?我认为您正在查找DbContext的
CurrentTransaction
属性:
var transaction = db.Database.CurrentTransaction;
然后,您可以进行如下检查:
using(var transaction = db.Database.CurrentTransaction ?? db.Database.BeginTransaction())
{
...
}
但是,如果并发方法正在使用事务,我不确定您如何知道何时提交该事务。您可以或可能应该使用创建环境事务作用域并管理与(SQL)的所有连接的事务的类,而不是使用实体框架的DbContext中的事务数据库被隐藏起来 如果您对
SqlCommand
使用精确的(区分大小写的)连接字符串,它甚至会在同一事务中放置一个直接的SqlCommand
。写入的消息也封装在同一事务中
它甚至可以同时管理到不同数据库的连接。它为此使用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();
}
}
}
公共类TransactionScopeCommandHandlerDecorator
:ICommandHandler
{
私有只读ICommandHandler装饰对象;
公共事务处理范围CommandHandlerDecorator(ICommandHandler decoratee)
{
this.decoree=decoree;
}
公共无效句柄(TCommand命令)
{
使用(var scope=new TransactionScope())
{
this.decoree.Handle(命令);
scope.Complete();
}
}
}
但是:正如qujck在评论中已经提到的,您缺少作为原子操作的ICommandHandler
的概念。一个commandhandler不应引用另一个commandhandler。这不仅对交易不利,而且考虑到这一点:
假设应用程序在增长,您将把一些命令处理程序重构为后台线程,该线程将在某些windows服务中运行。在此windows服务中,生活方式不可用。你现在需要一种生活方式。因为你的设计允许,顺便说一句,这太棒了!,您通常会将CommandHandler包装在一个文件中,以启动生命周期范围。在您当前的设计中,如果单个commandhandler引用其他commandhandler,您将遇到问题,因为每个commandhandler都将在其自己的作用域中创建,因此会注入其他commandhandler以外的DbContext
因此,您需要进行一些重新设计,制作CommandHandler,并为执行DbContext操作创建一个较低级别的抽象。阅读本文后,您缺少了一个@qujck。我不明白IQuery
和IDataQuery
之间的区别。两者都返回数据。为什么我们需要第二个接口?有一些例子吗?它们都返回数据,但这些不同的抽象只有在修饰代码时才重要。您需要一个拥有整个操作的抽象,一个可以用TransactionDecorator
之类的东西包装的抽象。您还可以使用横切关注点来修饰其他较低级别的抽象,例如authorizedCorator
或LoggingDecorator
,这些抽象与原子操作无关。@qujck终于得到了它。很好的解决方案,我一定会做的。谢谢这是正确的方法,+1如果您要为SqlCommand使用精确的(区分大小写的)connectionstring,它甚至会在同一事务中放置一个直接的SqlCommand
,这是不能保证的。小心,这会在负载下失败。@usr,你确定吗?我想你是的。你能指出一些关于这方面的文件吗?在某些情况下,我们严重依赖于此。。。非常感谢你的留言@Ric.Net非常感谢!我会考虑重新设计链命令。但我要说的是,这并不容易。在我的例子中,当客户端发送命令AddEntityA时,服务器应该将实体A添加到数据库中,然后还应该添加实体B。AddEntityB命令有很多规则和检查,并且有自己的装饰器,所以如果无法创建实体B,则实体A也不会添加到数据库中。这就是我在内部执行命令的原因command@Ric.Net如果在同一个TS中打开多个连接,您通常会很幸运,因为conn池可能与SQL Server具有相同的物理连接。然后,您就不会升级到MSDTC。但在负载情况下,池不一定能满足要求,可能会返回一个新的连接或另一个连接。然后随机升级(当应用程序必须最重要地工作时处于加载状态)。这是魔鬼。真是讨厌的设计。@usr讨厌的设计!brrrrrr。。。非常感谢您提供此信息。我会记住的!我非常支持你的答案,因为出于某种原因,我有6.0.0版本的EF,它没有这个属性。更新包后,EF转到6.1.3,属性在那里。非常感谢@Szer是的,它在EF中出现的时间不长:)我意识到你的答案对我的问题来说更直接,但我应该承认真正的答案是@Ric.Net所说的“糟糕的设计”。
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();
}
}
}