Description
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.