Skip to content

Case sensitive string comparison causes prepared transactions #2624

@jhartmann123

Description

@jhartmann123

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")

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions