Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An auto-enlisted transaction does not log the commit command #1510

Closed
vanbukin opened this issue Aug 29, 2024 · 3 comments
Closed

An auto-enlisted transaction does not log the commit command #1510

vanbukin opened this issue Aug 29, 2024 · 3 comments
Labels

Comments

@vanbukin
Copy link

vanbukin commented Aug 29, 2024

Software versions
MySqlConnector version: 2.3.7
Server type (MySQL, MariaDB, Aurora, etc.) and version: MySQL 8.0.35
.NET version: 8.0.8
ORM NuGet packages and versions:
Pomelo.EntityFrameworkCore.MySql 8.0.2
Microsoft.EntityFrameworkCore.Relational 8.0.8

Describe the bug

I have an extension method that adds DbContext and MySqlDataSource and configures EF Core to work with the MySqlDataSource.

Extension method

For the specified connection string, the connection string parameters - AutoEnlist and UseXaTransactions are forcibly set to true and false, respectively.

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MySqlConnector;

namespace MySql.Extensions;

public static class ServiceCollectionExtensions
{
  public static IServiceCollection AddCustomDbContext<TContext>(
      this IServiceCollection services,
      string connectionString,
      string applicationName,
      ServiceLifetime contextLifetime = ServiceLifetime.Scoped,
      ServiceLifetime optionsLifetime = ServiceLifetime.Scoped)
      where TContext : DbContext
  {
      ArgumentNullException.ThrowIfNull(services);
      var connectionStringBuilder = new MySqlConnectionStringBuilder(connectionString)
      {
          AutoEnlist = true,
          UseXaTransactions = false,
          AllowUserVariables = true,
          ApplicationName = applicationName
      };
      var runtimeConnectionString = connectionStringBuilder.ConnectionString;
      var dataSourceKeyProvider = new MySqlDataSourceKeyProvider<TContext>(applicationName);
      services.AddSingleton(dataSourceKeyProvider);
      services.AddKeyedMySqlDataSource(dataSourceKeyProvider.DataSourceKey, runtimeConnectionString);
      services.AddDbContext<TContext>(static (provider, builder) =>
          {
              var dataSourceKeyProvider = provider.GetRequiredService<MySqlDataSourceKeyProvider<TContext>>();
              var dataSourceKey = dataSourceKeyProvider.DataSourceKey;
              var hostEnvironment = provider.GetRequiredService<IHostEnvironment>();
              var mysqlDataSource = provider.GetRequiredKeyedService<MySqlDataSource>(dataSourceKey);
              builder.UseMySql(
                  mysqlDataSource,
                  Constants.MySqlServerVersion,
                  static mysql =>
                  {
                      mysql.MigrationsAssembly(typeof(TContext).Assembly.GetName().FullName);
                      mysql.UseMicrosoftJson();
                  });
              if (hostEnvironment.IsDevelopment())
              {
                  builder.EnableSensitiveDataLogging();
                  builder.EnableDetailedErrors();
              }
          },
          contextLifetime,
          optionsLifetime);
      return services;
  }

  private sealed class MySqlDataSourceKeyProvider<TContext>
      where TContext : DbContext
  {
      public MySqlDataSourceKeyProvider(string dataSourceKey)
      {
          if (string.IsNullOrWhiteSpace(dataSourceKey))
          {
              throw new ArgumentException("Value cannot be null or whitespace.", nameof(dataSourceKey));
          }

          DataSourceKey = dataSourceKey;
      }

      public string DataSourceKey { get; }
  }
}

I also have a global action filter that sets up a transaction for any ASP.NET Core MVC action using System.Transactions.

Action filter
using System;
using System.Threading.Tasks;
using System.Transactions;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Infrastructure.Mvc.ActionFilters;

public sealed class ExecuteInTransactionActionFilterAttribute : ActionFilterAttribute
{
    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(next);
        if (Transaction.Current is null)
        {
            using var transactionScope = new TransactionScope(
                TransactionScopeOption.Required,
                new TransactionOptions
                {
                    IsolationLevel = IsolationLevel.ReadCommitted
                },
                TransactionScopeAsyncFlowOption.Enabled);
            await next();
            transactionScope.Complete();
        }
        else
        {
            await next();
        }
    }
}

In the logs, I see all events and commands except for the transaction commit.
However, the commit itself is executed.
After debugging the code, I noticed that when OnCommit is called in the StandardEnlistedTransaction at the Connection object, all properties in the LoggingConfiguration contain NullLogger.

It appears as though the issue lies in this code segment related to closing the connection when using EnlistedTransaction.
A new MySqlConnection instance is created, but logging parameters are not passed to it.
After this, the new MySqlConnection instance begins using the global logging configuration, which by default contains NullLogger.

@vanbukin
Copy link
Author

vanbukin commented Aug 29, 2024

The issue has a workaround in the form of setting global logging parameters.
However, your documentation advises against doing this, starting from version 2.3.0.

@bgrainger
Copy link
Member

Thanks for the clear and detailed bug report!

@bgrainger
Copy link
Member

Fixed in 2.4.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

2 participants