-
Notifications
You must be signed in to change notification settings - Fork 254
Description
Situation
We've got two DbContexts, one of them pointing to a readonly replica server. The readonly DbContext has Enlist=False set in the connection string, so connections from that context shouldn't get enlisted in transactions.
With the update from .NET/EF.Core 6 to 7, we started to see prepared transactions, causing the usual exceptions when not enabled:
System.Transactions.TransactionAbortedException: The transaction has aborted.
---> Npgsql.PostgresException (0x80004005): 55000: prepared transactions are disabled
at Npgsql.Internal.NpgsqlConnector.<ReadMessage>g__ReadMessageLong|226_0(NpgsqlConnector connector, Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
at Npgsql.Internal.NpgsqlConnector.ExecuteInternalCommand(String query, Boolean async, CancellationToken cancellationToken)
at Npgsql.Internal.NpgsqlConnector.ExecuteInternalCommand(String query)
at Npgsql.VolatileResourceManager.Prepare(PreparingEnlistment preparingEnlistment)
Exception data:
Severity: ERROR
SqlState: 55000
MessageText: prepared transactions are disabled
Hint: Set max_prepared_transactions to a nonzero value.
File: twophase.c
Line: 386
Routine: MarkAsPreparing
The bug
When running a command within an ambient transaction, EF Core enlists the connection in the transaction. That happens in RelationalCommand.HandleAmbientTransactions() (link).
Note that nothing happens when CurrentAmbientTransaction is null.
In efcore.pg 6.0.9 NpgsqlRelationalConnection.CurrentAmbientTransaction used the following override (link):
public override Transaction? CurrentAmbientTransaction
=> DbConnection.Settings.Enlist ? Transaction.Current : null;So connections with Enlist=false were not enlisted in the transaction, as expected.
With efcore.pg 7, the override was changed to use a case sensitive string comparison (link):
public override Transaction? CurrentAmbientTransaction
=> ConnectionString is null || !ConnectionString.Contains("Enlist=false") ? Transaction.Current : null;We also use the NpgsqlConnectionStringBuilder.
new NpgsqlConnectionStringBuilder { Enlist = false }.ConnectionString returns Enlist=False, with a capital F.
Thus, the CurrentAmbientTransaction returns the current transaction instead of null. The connection gets enlisted in the transaction even though Enlist is set to false.
Workaround
new NpgsqlConnectionStringBuilder{ ... }.ConnectionString.Replace("=False", "=false")