禁用 DbContext.SaveChanges() 在 EntityFramework 6 中创建事务 - npgsq



推理

我的处境有些丑陋。

我需要DbTransaction,围绕潜在客户和其他人可以访问的"插件API",允许我在出现问题时回滚对数据库所做的更改(又名:发生异常(。

我曾经为此提供单一DbContext,但这证明了更复杂的代码具有挑战性,快速填充更改跟踪器(这需要:性能下降,内存使用量大等(。

我切换到一个新的设计,允许多个 DbContext 运行,但现在有一个问题,即带有 NPGSQL 的实体框架 6 在调用DbContext.SaveChanges()的那一刻抱怨。

A transaction is already in progress; nested/concurrent transactions aren't supported.

at Npgsql.NpgsqlConnection.BeginTransaction(IsolationLevel level)
at System.Data.Entity.Infrastructure.Interception.InternalDispatcher`1.Dispatch[TTarget,TInterceptionContext,TResult](TTarget target, Func`3 operation, TInterceptionContext interceptionContext, Action`3 executing, Action`3 executed)
at System.Data.Entity.Infrastructure.Interception.DbConnectionDispatcher.BeginTransaction(DbConnection connection, BeginTransactionInterceptionContext interceptionContext)
at System.Data.Entity.Core.EntityClient.EntityConnection.BeginDbTransaction(IsolationLevel isolationLevel)

简化的示例代码

// Example. Resides in another assembly
async Task Plugin(DbConnection dbConnection)
{
using (var cntxt = new Database.Context(dbConnection))
{
cntxt.LogMessages.Add("Fancy Log Message");
await cntxtb.SaveChangesAsync();
}
}
// Simplified version of what actually happens
void PluginCaller()
{
using var dbConnection = Database.Context.Connection(usereadonly: false);
using var dbTransaction = dbConnection.BeginTransaction();
Plugin(dbConnection).Wait();
}

现在的问题是

我怎样才能围绕插件正在做的事情强制执行大型事务,以防止有故障的插件损坏数据库(从这个意义上说,损坏意味着:下次插件运行时,一切都很好,但有些项目现在可能会从插件创建两次,导致更多错误(

注意:

我已经尝试为此使用TransactionScope,但这产生了相同的异常。

如果你可以对插件作者施加规则(而不是使用不可变的依赖项(,要求他们获取现有事务,如下所述:

using (var context = new BloggingContext(conn, contextOwnsConnection: false(( { 上下文。Database.UseTransaction(sqlTxn(;//... 上下文。保存更改((; }

甚至可以将其包装在实用程序函数中并将其作为参数传递而不是dbConnection

async Task Plugin(DbContextGetter getDbContext( { using (var cntxt = getDbContext((( {//.... } }

作为旁注,我怀疑插件 - 一旦它可以访问DbContextDbConnection- 可能会执行原始sql命令。默认情况下,命令包装在事务中,除非它们伪装成查询。不确定这是否真的可能,但这是需要考虑的严重攻击媒介。为了规避它,作为一般的保证,您可以安装拦截器来捕获缺少command.DbTransaction的任何事件。

将事务与 EntityFramework 一起使用时,可以访问两种方法:

Commit(),用于完成您在事务中完成的工作。

Rollback(),用于丢弃您在事务中所做的所有更改。

如果您使用的是DbContext,您可以执行以下操作来防止任何未经处理的异常损坏数据库,并使用事务安全地回滚:

using (var transaction = yourDbContext.BeginTransaction())
{
try
{
// Your code here...
transaction.Commit(); // And you finalize your transaction here
}
catch (Exception e)
{
// Your potential error handling here
transaction.Rollback(); // And you rollback all the changes here
}
}