Original
About 45 days ago I updated a project I have that was on .NET 5 to .NET 6. It's using ASP.NET Core 6 and Entity Framework Core 6 across eight websites. All eight websites use a shared configuration process and get their configurations from AWS Parameter Store.
Since the update, there are random exceptions happening with the following message:
The specified transaction is not associated with the current connection. Only transactions associated with the current connection may be used.
Retrying the request right after usually resolves the issue, but not always. I don't remember having any such issues under .NET 5, ASP.NET Core 5 and Entity Framework Core 5. I'm not sure how to resolve this. Here is how I'm setting up and configuring EF, does anyone notice anything that could be the cause? The code didn't really change between 5 and 6...:
public static IServiceCollection AddApplicationDbContextPool(
this IServiceCollection services,
IConfig config,
int poolSize = 1024) {
if (config is null) {
throw new ArgumentNullException(nameof(config));
}
services.AddDbContextPool<ApplicationContext>(
_ => {
_.UseSqlServer(config.DefaultConnection,
__ => {
__.UseNetTopologySuite();
if (!config.IsDevelopment) {
return;
}
__.CommandTimeout(int.MaxValue);
});
if (!config.IsDevelopment) {
return;
}
_.EnableDetailedErrors()
.EnableSensitiveDataLogging();
}, poolSize);
services.AddDbContextPool<SerilogContext>(
_ => {
_.UseSqlServer(config.SerilogConnection,
__ => {
__.UseNetTopologySuite();// may be unnecessary, but keeping until I verify.
if (!config.IsDevelopment) {
return;
}
__.CommandTimeout(int.MaxValue);
});
if (!config.IsDevelopment) {
return;
}
_.EnableDetailedErrors()
.EnableSensitiveDataLogging();
});
NtsGeometryServices.Instance = new NtsGeometryServices(
NetTopologySuite.Geometries.Implementation.CoordinateArraySequenceFactory.Instance,
new PrecisionModel(1000d),
4326,
GeometryOverlay.NG,
new CoordinateEqualityComparer()
);
return services;
}
The backing database is SQL Server 2019. Not sure if it matters, but with the update the database server was swapped with a new instance that was more tightly tuned and configured than the previous one and is also on faster hardware than the previous one. I doubt it's the database that's the issue, something about the applications interacting with it is.
There is one app that's actually using Dapper to interact with the database since it's the hottest app in the group. Here is the Dapper configuration, and the specific app is using a Singleton scope (I need to remove the transient, nothing is using it anymore):
public static IServiceCollection AddApplicationDapper(
this IServiceCollection services,
IConfig config,
ServiceLifetime serviceLifetime = ServiceLifetime.Transient) {
if (config is null) {
throw new ArgumentNullException(nameof(config));
}
SqlMapper.AddTypeHandler(new GeometryHandler<Point>(true));
SqlMapper.AddTypeHandler(new GeometryHandler<Polygon>(true));
return serviceLifetime == ServiceLifetime.Singleton
? services.AddSingleton(
_ => new SqlConnection(config.DefaultConnection))
: services.AddTransient(
_ => new SqlConnection(config.DefaultConnection));
}
Could it be the use of async/await throughout the apps? I do use .ConfigureAwait(false)
pretty much everywhere, could that thread transfer be the cause? This also didn't change much between 5 and 6.
I thought it was the EF Core Triggered package and have since removed it, but the exceptions continue to be thrown. Help will be highly appreciated!
Update #1
@Dai
To clarify the Dapper usage with a singleton SqlConnection
instance, it is only used in one of the apps, which is technically an ASP.NET Core app for common management with the other ASP.NET Core apps, but it doesn't listen to web traffic at all. Instead it is an IoT listener and handler where it registers a couple of IHostedServices
classes as singletons. The IoT services listen for reports from specific devices then pass it up to a handling service. Only the handling service is given a SqlConnection
instance, so in this specific case it is a 1-to-1 use, so a singleton SqlConnection
works fine. This is how this project has been since the days of .NET Core 2.0.
It's not this project that's getting the exceptions, it is the other ones using EF Core to interact with the database that are. I doubt a single instance of SqlConnection
used in a single handler service for the lifetime of the IoT app is the cause.
@Gert Arnold
I'm 99.9991% sure that I am awaiting everywhere. At the very least ReSharper will yell at me if I don't.
Update #2
System.InvalidOperationException: The specified transaction is not associated with the current connection. Only transactions associated with the current connection may be used.
at Microsoft.EntityFrameworkCore.Storage.RelationalTransaction..ctor(IRelationalConnection connection, DbTransaction transaction, Guid transactionId, IDiagnosticsLogger`1 logger, Boolean transactionOwned, ISqlGenerationHelper sqlGenerationHelper)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerTransactionFactory.Create(IRelationalConnection connection, DbTransaction transaction, Guid transactionId, IDiagnosticsLogger`1 logger, Boolean transactionOwned)
at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.BeginTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.BeginTransactionAsync(CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
at REDACTED.App.Devices.Locate.CommandHandler.Handle(Command command, CancellationToken cancellationToken) in E:\Software Development\REDACTED\REDACTED.App\Features\Devices\Locate.cs:line 69