精简版
我们希望禁用整个 Entity Framework Core 8
System.Transactions.TransactionScope
的环境事务自动登记 (DbContext
)。 DbContext
不应该永远 被加入到已经正在进行的环境事务中。
我们不想在访问
TransactionScope
的代码中的任何地方显式地创建一个带有 TransactionScopeOption.Suppress
的新 DbContext
,因此我们希望全局禁用它。我们希望抽象出这个实现细节并降低代码复杂性。开发者在访问 DbContext
时不应该关心这个。
更长的版本,更多上下文
我们有两个不同的 Entity Framework Core
DbContext
,它们连接到两个不同的数据库。两个数据库之一使用“只读”,因此我们不需要在两个数据库上进行(分布式)事务。对于此数据库,我们希望禁用环境事务登记。
我们在创建 TransactionScope
的地方有一个 装饰器模式。在装饰器内部,两个数据库都可以使用,并且默认情况下,两个
DbContext
都加入环境事务。我们想要禁用只读数据库的登记。我们已经尝试过
Enlist=false
属性来实现这一点。
但这似乎不适用于实体框架,因为 EF 不考虑此连接字符串属性。
DbCommandInterceptor
并重写
ReaderExecuting
方法并将连接的事务设置为null
:public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
command.Connection?.EnlistTransaction(null);
return base.ReaderExecuting(command, eventData, result);
}
这会引发异常,表明连接已登记一个必须首先完成的事务。
TransactionScope
并使用
TransactionScopeOption.Suppress
。但我们不确定如何正确处理拦截器内的 TransactionScope
。这也感觉很hacky,我们不想通过不正确的实现引入资源泄漏。
DbConnectionInterceptor
并在拦截器的方法中启动抑制范围。下面是一个示例实现:
public class SuppressTransactionScopeConnectionInterceptor : DbConnectionInterceptor, IDisposable
{
TransactionScope? _scope;
TransactionScope? _scopeAsync;
public override InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result)
{
_scope = new TransactionScope(TransactionScopeOption.Suppress);
return base.ConnectionOpening(connection, eventData, result);
}
public override void ConnectionOpened(DbConnection connection, ConnectionEndEventData eventData)
{
base.ConnectionOpened(connection, eventData);
_scope?.Dispose();
_scope = null;
}
public override void ConnectionFailed(DbConnection connection, ConnectionErrorEventData eventData)
{
base.ConnectionFailed(connection, eventData);
_scope?.Dispose();
_scope = null;
}
public override ValueTask<InterceptionResult> ConnectionOpeningAsync(DbConnection connection, ConnectionEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default)
{
_scopeAsync = new TransactionScope(TransactionScopeAsyncFlowOption.Suppress);
return base.ConnectionOpeningAsync(connection, eventData, result, cancellationToken);
}
public override async Task ConnectionOpenedAsync(DbConnection connection, ConnectionEndEventData eventData,
CancellationToken cancellationToken = new CancellationToken())
{
await base.ConnectionOpenedAsync(connection, eventData, cancellationToken);
_scopeAsync?.Dispose();
_scopeAsync = null;
}
public override async Task ConnectionFailedAsync(DbConnection connection, ConnectionErrorEventData eventData,
CancellationToken cancellationToken = new CancellationToken())
{
await base.ConnectionFailedAsync(connection, eventData, cancellationToken);
_scopeAsync?.Dispose();
_scopeAsync = null;
}
public void Dispose()
{
_scope?.Dispose();
_scope = null;
_scopeAsync?.Dispose();
_scopeAsync = null;
}
}
要在
DbContext
配置期间注册此拦截器,请使用以下命令:
services.AddScoped<SuppressTransactionScopeConnectionInterceptor>();
services.AddDbContext<MyDbContext>((sp, o) => o.UseSqlServer(
"connection string")
.AddInterceptors(sp.GetRequiredService<SuppressTransactionScopeConnectionInterceptor>()));
我目前没有最佳的测试环境来验证此代码,但我希望它能按预期运行。如果您遇到任何问题,请随时联系我们。