Skip to content

SqlConnection.CreateCommand() - Auto-Enlist in Pending Local Transaction #28

Open
@bgribaudo

Description

@bgribaudo

When CreateCommand() is called on a SqlConnection that is in a transaction, the SqlCommand returned by that method is not associated with the connection's transaction. Instead, the command must be explicitly informed of the transaction or an exception similar to the following will be thrown when the command is executed:

MethodInvocationException: Exception calling "ExecuteReader" with "0" argument(s): "ExecuteReader requires the command to have a transaction when the connection assigned to the command is in a pending local transaction. The Transaction property of the command has not been initialized."

Cost of the Manual Wire-Up

  • The need to manually associate the command with the connection's transaction adds a small amount of code clutter.

      using (var connection = new SqlConnection(connectionString)) {
          connection.Open();
          var transaction = connection.BeginTransaction();
    
          using (var command = connection.CreateCommand()) {
              command.Transaction = transaction; // code clutter
      }
    
  • More significantly, the need to explicitly set up this association when a transaction is used means that modifying a high-level component to use transactions requires code changes to lower-level components sharing the same SqlConnection, even when these lower-level components are transaction-agnostic.

      class Service
      {
          public void Process()
          {
              /*
                Modifying Process() to use transactions isn't as simple as uncommenting the below 
                because component also needs to be modified to wire up its SqlCommand to use the 
                transaction even though component may not care whether it is in a transaction. 
             */
              
              // using (var transaction = connection.BeginTransaction())
              // {
                  component.Process(connection);
    
              //    transaction.Commit();
              // }
          }
      }
    
  • All methods executing SqlCommands on the connection when the transaction is in use must have access to the transaction object. SqlConnection does not provide a public property exposing its transaction, so the object either must be passed around or accessed via reflection.

      using (var command = connection.CreateCommand()) {
          command.Transaction = connection.Transaction; // not possible because connection does not publically expose the transaction
      }
    

Proposal

When CreateCommand() is called on a SqlConnection in a transaction, the SqlCommand it returns should be associated with the connection's transaction, eliminating the need for the explicit wire-up.

[Added: 2019-06-03]:
Alternately (perhaps better yet), change command execution so that it automatically uses the transaction associated with the connection when it's executed. With this approach, any command (whether or not created by CreateCommand()) will execute against the proper transaction. Existing logic requires that the connection's transaction is used; the change proposed here is for the command to automatically use this transaction vs. the current behavior of the command throwing an exception when the transaction hasn't been explicitly referenced.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Enhancement 💡Issues that are feature requests for the drivers we maintain.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions