首先,请为 gds03 的回答点赞。因为没有它我就不会走到这一步。
我也为“关闭事务”做出了贡献,并为 IDataReader/DbDataReader 情况提供了正确的时机。
基本上,在 IDataReader/DbDataReader 情况下,您不会关闭“ReaderExecuted”(Async) 方法上的事务(强调 Executed 的“ed”),而是让它“通过”(覆盖)DataReaderDisposing。
但是,如果您阅读了其他一些答案(此处)(和 cmets),我认为 SetEndTransaction 是 .. 不从连接池中获取巫毒的重要部分(如果您可能不会关闭交易) (对我而言)未提交的)。
using System.Data;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;
namespace My.Interceptors
{
public class IsolationLevelInterceptor : DbCommandInterceptor
{
private IsolationLevel _isolationLevel;
public IsolationLevelInterceptor(IsolationLevel level)
{
_isolationLevel = level;
}
//[ThreadStatic]
//private DbCommand _command;
public override InterceptionResult DataReaderDisposing(DbCommand command, DataReaderDisposingEventData eventData, InterceptionResult result)
{
InterceptionResult returnItem = base.DataReaderDisposing(command, eventData, result);
SetEndTransaction(command);
return returnItem;
}
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
{
SetStartTransaction(command);
InterceptionResult<DbDataReader> returnItem = base.ReaderExecuting(command, eventData, result);
return returnItem;
}
public override DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result)
{
DbDataReader returnItem = base.ReaderExecuted(command, eventData, result);
//SetEndTransaction(command); // DO NOT DO THIS HERE .. datareader still open and working .. fall back on DataReaderDisposing... you don't really need this override, but left in to show the possible issue.
return returnItem;
}
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
{
SetStartTransaction(command);
ValueTask<InterceptionResult<DbDataReader>> returnItem = base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
return returnItem;
}
public override ValueTask<DbDataReader> ReaderExecutedAsync(DbCommand command, CommandExecutedEventData eventData, DbDataReader result, CancellationToken cancellationToken = default)
{
ValueTask<DbDataReader> returnItem = base.ReaderExecutedAsync(command, eventData, result, cancellationToken);
//SetEndTransaction(command); // DO NOT DO THIS HERE .. datareader still open and working .. fall back on DataReaderDisposing... you don't really need this override, but left in to show the possible issue.
return returnItem;
}
public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> interceptionContext)
{
SetStartTransaction(command);
InterceptionResult<object> returnItem = base.ScalarExecuting(command, eventData, interceptionContext);
return returnItem;
}
public override object ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object result)
{
SetEndTransaction(command);
object returnItem = base.ScalarExecuted(command, eventData, result);
return returnItem;
}
public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<object> result, CancellationToken cancellationToken = default)
{
SetStartTransaction(command);
ValueTask<InterceptionResult<object>> returnItem = base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
return returnItem;
}
public override ValueTask<object> ScalarExecutedAsync(DbCommand command, CommandExecutedEventData eventData, object result, CancellationToken cancellationToken = default)
{
SetEndTransaction(command);
ValueTask<object> returnItem = base.ScalarExecutedAsync(command, eventData, result, cancellationToken);
return returnItem;
}
/* start maybe not needed on queries that only do "reading", but listed here anyways */
public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result)
{
SetStartTransaction(command);
InterceptionResult<int> returnItem = base.NonQueryExecuting(command, eventData, result);
return returnItem;
}
public override int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result)
{
int returnValue = base.NonQueryExecuted(command, eventData, result);
SetEndTransaction(command);
return returnValue;
}
public override ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
{
SetStartTransaction(command);
ValueTask<InterceptionResult<int>> returnItem = base.NonQueryExecutingAsync(command, eventData, result, cancellationToken);
return returnItem;
}
public override ValueTask<int> NonQueryExecutedAsync(DbCommand command, CommandExecutedEventData eventData, int result, CancellationToken cancellationToken = default)
{
ValueTask<int> returnValue = base.NonQueryExecutedAsync(command, eventData, result, cancellationToken);
SetEndTransaction(command);
return returnValue;
}
/* end maybe not needed on queries that only do "reading", but listed here anyways */
private void SetStartTransaction(DbCommand command)
{
if (command != null)
{
if (command.Transaction == null)
{
DbTransaction t = command.Connection.BeginTransaction(_isolationLevel);
command.Transaction = t;
//_command = command;
}
}
}
private void SetEndTransaction(DbCommand command)
{
if (command != null)
{
if (command.Transaction != null)
{
command.Transaction.Commit();
//_command = command;
}
command.Dispose();
}
}
}
}
这篇文章有助于“了解”所有“ing”和“ed”方法。
https://lizzy-gallagher.github.io/query-interception-entity-framework/
请注意我的答案是 EntityFrameworkCore (3.1.+),但我认为它将“反向移植”到 EF-for-DN-Framework。
我的回答中更重要的部分是某些方法的“时机”……尤其是 IDataReader/DbDataReader 方法。