Bug #116257 Prepared statement command with OpenTelemetry enabled causes unhandled exception
Submitted: 27 Sep 20:00 Modified: 30 Sep 13:39
Reporter: Malin Olafson Email Updates:
Status: Verified Impact on me:
None 
Category:Connector / NET Severity:S2 (Serious)
Version:8.3.0 and higher, 9.0 OS:Any
Assigned to: CPU Architecture:Any
Tags: opentelemetery

[27 Sep 20:00] Malin Olafson
Description:
If the same instance of a MySqlCommand object has an execute call made on it more than once with opentelemtery enabled ("connector-net" activity source has a listener set on it), the second execute call of the command causes an unhandled exception and the command execute call fails. Unhandled exception information: Object reference not set to an instance of an object. at MySql.Data.MySqlClient.MySqlConnection.set_Reader(MySqlDataReader value) in MySql.Data.MySqlClient\MySqlConnection.cs:line 63

To stop this exception, opentelemetery must be disabled, there cannot be any listeners set on the "connector-net" activity source. 

When using prepared statements it's common to call an execute method on the same command instance more than once. This use case was how this issue was discovered.

How to repeat:
// Using the code below with the following Nuget packages will cause the exception to occur
// Nuget packages used:
//  <PackageReference Include="MySql.Data" Version="9.0.0" />
//  <PackageReference Include = "OpenTelemetry" Version = "1.9.0" />
//  <PackageReference Include = "OpenTelemetry.Exporter.Console" Version = "1.9.0" />
using System.Data.Common;
using System.Data;
using OpenTelemetry;
using OpenTelemetry.Trace;
using MySql.Data.MySqlClient;

namespace TestAppExample
{
    public class Program
    {
        public static void Main()
        {
            var tracerProvider = Sdk.CreateTracerProviderBuilder()
                .AddConsoleExporter()
                .AddSource("connector-net")
                .Build();

            DbProviderFactories.RegisterFactory("MySql.Data.MySqlClient", MySql.Data.MySqlClient.MySqlClientFactory.Instance);

            RunPrepareStatement();

            tracerProvider.Dispose();
        }
        static void RunPrepareStatement()
        {
            DbProviderFactory factory = DbProviderFactories.GetFactory("MySql.Data.MySqlClient");

            string connectionString = "Server=localhost;Port=3306;Initial Catalog=dbo;User Id=test_user;Password=password;";

            using MySqlConnection connection = new MySqlConnection(connectionString);
            connection.Open();

            string selectQuery = "SELECT * FROM BOOK WHERE Color=@val1";

            using var command = new MySqlCommand(selectQuery, connection);
            command.CommandType = CommandType.Text;
            command.Connection = connection;

            DbParameter color = command.CreateParameter();
            color.ParameterName = "@val1";

            command.Parameters.Add(color);
            command.Prepare();

            color.Value = "Black";
            string results = string.Empty;

            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    results = results + $"name: {reader.GetString(1)} ";
                }
            }

            // Run prepared command again with different parameter value
            color.Value = "Red";

            // Running command second time results in an unhandled exception
            using (var reader = command.ExecuteReader(System.Data.CommandBehavior.CloseConnection))
            {
                while (reader.Read())
                {
                    results = results + $"name: {reader.GetString(1)}";
                }
            }
        }
    }
}

Suggested fix:
If a command instance is run more than once do not add another sql attribute to the command. A `traceparent` attribute is being added to the command each time an activity is started.

Support configuration for whether or not the `traceparent` is added to the sql attributes as part of the start activity.
[27 Sep 20:10] Malin Olafson
MySql documentation describes this use case of prepared statements:

https://dev.mysql.com/doc/connector-net/en/connector-net-programming-prepared.html

For subsequent executions, you need only modify the values of the parameters and call the execute method again, there is no need to set the CommandText property or redefine the parameters.
[30 Sep 13:39] MySQL Verification Team
Hello Malin Olafson,

Thank you for the report and test case

regards,
Umesh