是否可以全局禁用 Entity Framework Core 8 中环境事务的登记?

问题描述 投票:0回答:1

精简版

我们希望禁用整个 Entity Framework Core 8

System.Transactions.TransactionScope
的环境事务自动登记 (
DbContext
)。
DbContext
不应该永远 被加入到已经正在进行的环境事务中。

我们不想在访问

TransactionScope
的代码中的任何地方显式地创建一个带有
TransactionScopeOption.Suppress
的新
DbContext
,因此我们希望全局禁用它。我们希望抽象出这个实现细节并降低代码复杂性。开发者在访问
DbContext
时不应该关心这个。

更长的版本,更多上下文

我们有两个不同的 Entity Framework Core

DbContext
,它们连接到两个不同的数据库。两个数据库之一使用“只读”,因此我们不需要在两个数据库上进行(分布式)事务。对于此数据库,我们希望禁用环境事务登记。 我们在创建

TransactionScope

的地方有一个 装饰器模式。在装饰器内部,两个数据库都可以使用,并且默认情况下,两个

DbContext
都加入环境事务。我们想要禁用只读数据库的登记。

我们已经尝试过

    对于 ADO.NET,可以通过在连接字符串中指定
  1. Enlist=false

    属性来实现这一点。

    但这似乎不适用于实体框架,因为 EF 不考虑此连接字符串属性。

  2. 我们尝试创建一个
  3. 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);
    }
    

    这会引发异常,表明连接已登记一个必须首先完成的事务。

  4. 我们考虑在拦截器内创建一个新的
  5. TransactionScope

    并使用

    TransactionScopeOption.Suppress
    。但我们不确定如何正确处理拦截器内的
    TransactionScope
    。这也感觉很hacky,我们不想通过不正确的实现引入资源泄漏。
    
    

c# entity-framework-core transactions transactionscope
1个回答
0
投票
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>()));

我目前没有最佳的测试环境来验证此代码,但我希望它能按预期运行。如果您遇到任何问题,请随时联系我们。

© www.soinside.com 2019 - 2024. All rights reserved.