Skip to content

Commit 89e5b42

Browse files
committed
Add IgnoreCommandTransaction. Fixes #474
1 parent 0924e30 commit 89e5b42

File tree

9 files changed

+126
-15
lines changed

9 files changed

+126
-15
lines changed

docs/content/connection-options.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,12 @@ These are the other options that MySqlConnector supports. They are set to sensib
196196
See the note in the <a href="https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.commandtimeout">Microsoft documentation</a>
197197
for more explanation of how this is determined.</td>
198198
</tr>
199+
<tr>
200+
<td>IgnoreCommandTransaction, Ignore Command Transaction</td>
201+
<td>false</td>
202+
<td>If <code>true</code>, the value of <code>MySqlCommand.Transaction</code> is ignored when commands are executed.
203+
This matches the Connector/NET behaviour and can make porting code easier. For more information, see <a href="https://github.com/mysql-net/MySqlConnector/issues/474">Issue 474</a>.</td>
204+
</tr>
199205
<tr>
200206
<td>Keep Alive, Keepalive</td>
201207
<td>0</td>

docs/content/tutorials/migrating-from-connector-net.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ object to be created. See [#331](https://github.com/mysql-net/MySqlConnector/iss
7474

7575
Connector/NET allows a command to be executed even when `MySqlCommand.Transaction` references a commited, rolled back, or
7676
disposed `MySqlTransaction`. MySqlConnector will throw an `InvalidOperationException` if the `MySqlCommand.Transaction`
77-
property doesn't reference the active transaction. See [#333](https://github.com/mysql-net/MySqlConnector/issues/333) for more details.
77+
property doesn't reference the active transaction. To disable this strict validation, set <code>IgnoreCommandTransaction=true</code>
78+
in the connection string. See [Issue 474](https://github.com/mysql-net/MySqlConnector/issues/474) for more details.
7879

7980
### Exceptions
8081

@@ -104,7 +105,7 @@ The following bugs in Connector/NET are fixed by switching to MySqlConnector.
104105
* [#81650](https://bugs.mysql.com/bug.php?id=81650), [#88962](https://bugs.mysql.com/bug.php?id=88962): `Server` connection string option may now contain multiple, comma separated hosts that will be tried in order until a connection succeeds
105106
* [#83229](https://bugs.mysql.com/bug.php?id=83329): "Unknown command" exception inserting large blob with UseCompression=True
106107
* [#84220](https://bugs.mysql.com/bug.php?id=84220): Cannot call a stored procedure with `.` in its name
107-
* [#84701](https://bugs.mysql.com/bug.php?id=84701): Can't create a paramter using a 64-bit enum with a value greater than int.MaxValue
108+
* [#84701](https://bugs.mysql.com/bug.php?id=84701): Can't create a parameter using a 64-bit enum with a value greater than int.MaxValue
108109
* [#85185](https://bugs.mysql.com/bug.php?id=85185): `ConnectionReset=True` does not preserve connection charset
109110
* [#86263](https://bugs.mysql.com/bug.php?id=86263): Transaction isolation level affects all transactions in session
110111
* [#87307](https://bugs.mysql.com/bug.php?id=87307): NextResult hangs instead of timing out

src/MySqlConnector/Core/ConnectionSettings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public ConnectionSettings(MySqlConnectionStringBuilder csb)
5757
ConvertZeroDateTime = csb.ConvertZeroDateTime;
5858
DefaultCommandTimeout = (int) csb.DefaultCommandTimeout;
5959
ForceSynchronous = csb.ForceSynchronous;
60+
IgnoreCommandTransaction = csb.IgnoreCommandTransaction;
6061
Keepalive = csb.Keepalive;
6162
OldGuids = csb.OldGuids;
6263
PersistSecurityInfo = csb.PersistSecurityInfo;
@@ -106,6 +107,7 @@ public ConnectionSettings(MySqlConnectionStringBuilder csb)
106107
public bool ConvertZeroDateTime { get; }
107108
public int DefaultCommandTimeout { get; }
108109
public bool ForceSynchronous { get; }
110+
public bool IgnoreCommandTransaction { get; }
109111
public uint Keepalive { get; }
110112
public bool OldGuids { get; }
111113
public bool PersistSecurityInfo { get; }

src/MySqlConnector/MySql.Data.MySqlClient/MySqlCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,8 @@ private bool IsValid(out Exception exception)
261261
exception = new InvalidOperationException("Connection property must be non-null.");
262262
else if (Connection.State != ConnectionState.Open && Connection.State != ConnectionState.Connecting)
263263
exception = new InvalidOperationException("Connection must be Open; current state is {0}".FormatInvariant(Connection.State));
264-
else if (Transaction != Connection.CurrentTransaction)
265-
exception = new InvalidOperationException("The transaction associated with this command is not the connection's active transaction.");
264+
else if (!Connection.IgnoreCommandTransaction && Transaction != Connection.CurrentTransaction)
265+
exception = new InvalidOperationException("The transaction associated with this command is not the connection's active transaction; see https://github.com/mysql-net/MySqlConnector/issues/474");
266266
else if (string.IsNullOrWhiteSpace(CommandText))
267267
exception = new InvalidOperationException("CommandText must be specified");
268268
return exception == null;

src/MySqlConnector/MySql.Data.MySqlClient/MySqlConnection.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ internal async Task<CachedProcedure> GetCachedProcedure(IOBehavior ioBehavior, s
349349
internal bool AllowUserVariables => m_connectionSettings.AllowUserVariables;
350350
internal bool ConvertZeroDateTime => m_connectionSettings.ConvertZeroDateTime;
351351
internal int DefaultCommandTimeout => GetConnectionSettings().DefaultCommandTimeout;
352+
internal bool IgnoreCommandTransaction => m_connectionSettings.IgnoreCommandTransaction;
352353
internal bool OldGuids => m_connectionSettings.OldGuids;
353354
internal bool TreatTinyAsBoolean => m_connectionSettings.TreatTinyAsBoolean;
354355
internal IOBehavior AsyncIOBehavior => GetConnectionSettings().ForceSynchronous ? IOBehavior.Synchronous : IOBehavior.Asynchronous;

src/MySqlConnector/MySql.Data.MySqlClient/MySqlConnectionStringBuilder.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ public bool ForceSynchronous
171171
set => MySqlConnectionStringOption.ForceSynchronous.SetValue(this, value);
172172
}
173173

174+
public bool IgnoreCommandTransaction
175+
{
176+
get => MySqlConnectionStringOption.IgnoreCommandTransaction.GetValue(this);
177+
set => MySqlConnectionStringOption.IgnoreCommandTransaction.SetValue(this, value);
178+
}
179+
174180
public uint Keepalive
175181
{
176182
get => MySqlConnectionStringOption.Keepalive.GetValue(this);
@@ -290,6 +296,7 @@ internal abstract class MySqlConnectionStringOption
290296
public static readonly MySqlConnectionStringOption<bool> ConvertZeroDateTime;
291297
public static readonly MySqlConnectionStringOption<uint> DefaultCommandTimeout;
292298
public static readonly MySqlConnectionStringOption<bool> ForceSynchronous;
299+
public static readonly MySqlConnectionStringOption<bool> IgnoreCommandTransaction;
293300
public static readonly MySqlConnectionStringOption<uint> Keepalive;
294301
public static readonly MySqlConnectionStringOption<bool> OldGuids;
295302
public static readonly MySqlConnectionStringOption<bool> PersistSecurityInfo;
@@ -428,6 +435,10 @@ static MySqlConnectionStringOption()
428435
keys: new[] { "ForceSynchronous" },
429436
defaultValue: false));
430437

438+
AddOption(IgnoreCommandTransaction = new MySqlConnectionStringOption<bool>(
439+
keys: new[] { "IgnoreCommandTransaction", "Ignore Command Transaction" },
440+
defaultValue: false));
441+
431442
AddOption(Keepalive = new MySqlConnectionStringOption<uint>(
432443
keys: new[] { "Keep Alive", "Keepalive" },
433444
defaultValue: 0u));

tests/MySqlConnector.Tests/MySqlConnectionStringBuilderTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public void Defaults()
3232
Assert.Equal(0u, csb.ConnectionIdlePingTime);
3333
Assert.Equal(180u, csb.ConnectionIdleTimeout);
3434
Assert.False(csb.ForceSynchronous);
35+
Assert.False(csb.IgnoreCommandTransaction);
3536
Assert.Null(csb.CACertificateFile);
3637
Assert.Equal(MySqlLoadBalance.RoundRobin, csb.LoadBalance);
3738
#endif
@@ -80,6 +81,7 @@ public void ParseConnectionString()
8081
"connection idle ping time=60;" +
8182
"connectionidletimeout=30;" +
8283
"forcesynchronous=true;" +
84+
"ignore command transaction=true;" +
8385
"ca certificate file=ca.pem;" +
8486
"allow public key retrieval = true;" +
8587
"server rsa public key file=rsa.pem;" +
@@ -113,6 +115,7 @@ public void ParseConnectionString()
113115
Assert.Equal(60u, csb.ConnectionIdlePingTime);
114116
Assert.Equal(30u, csb.ConnectionIdleTimeout);
115117
Assert.True(csb.ForceSynchronous);
118+
Assert.True(csb.IgnoreCommandTransaction);
116119
Assert.Equal("ca.pem", csb.CACertificateFile);
117120
Assert.True(csb.AllowPublicKeyRetrieval);
118121
Assert.Equal("rsa.pem", csb.ServerRsaPublicKeyFile);

tests/SideBySide/CommandTests.cs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ public void CreateCommandSetsConnection()
2222
}
2323
}
2424

25+
[Fact]
26+
public void CreateCommandDoesNotSetTransaction()
27+
{
28+
using (var connection = new MySqlConnection(AppConfig.ConnectionString))
29+
{
30+
connection.Open();
31+
using (connection.BeginTransaction())
32+
using (var cmd = connection.CreateCommand())
33+
{
34+
Assert.Null(cmd.Transaction);
35+
}
36+
}
37+
}
38+
2539
[Fact]
2640
public void ExecuteReaderRequiresConnection()
2741
{
@@ -87,6 +101,89 @@ public async Task ExecuteNonQueryReturnValue()
87101
}
88102
}
89103

104+
[SkippableFact(Baseline = "https://bugs.mysql.com/bug.php?id=88611")]
105+
public void CommandTransactionMustBeSet()
106+
{
107+
using (var connection = new MySqlConnection(AppConfig.ConnectionString))
108+
{
109+
connection.Open();
110+
using (var transaction = connection.BeginTransaction())
111+
using (var command = connection.CreateCommand())
112+
{
113+
command.CommandText = "SELECT 1;";
114+
Assert.Throws<InvalidOperationException>(() => command.ExecuteScalar());
115+
116+
command.Transaction = transaction;
117+
Assert.Equal(1L, command.ExecuteScalar());
118+
}
119+
}
120+
}
121+
122+
[Fact]
123+
public void IgnoreCommandTransactionIgnoresNull()
124+
{
125+
using (var connection = new MySqlConnection(GetIgnoreCommandTransactionConnectionString()))
126+
{
127+
connection.Open();
128+
using (connection.BeginTransaction())
129+
using (var command = connection.CreateCommand())
130+
{
131+
command.CommandText = "SELECT 1;";
132+
Assert.Equal(1L, command.ExecuteScalar());
133+
}
134+
}
135+
}
136+
137+
[Fact]
138+
public void IgnoreCommandTransactionIgnoresDisposedTransaction()
139+
{
140+
using (var connection = new MySqlConnection(GetIgnoreCommandTransactionConnectionString()))
141+
{
142+
connection.Open();
143+
144+
var transaction = connection.BeginTransaction();
145+
transaction.Commit();
146+
transaction.Dispose();
147+
148+
using (var command = connection.CreateCommand())
149+
{
150+
command.CommandText = "SELECT 1;";
151+
command.Transaction = transaction;
152+
Assert.Equal(1L, command.ExecuteScalar());
153+
}
154+
}
155+
}
156+
157+
[Fact]
158+
public void IgnoreCommandTransactionIgnoresDifferentTransaction()
159+
{
160+
using (var connection1 = new MySqlConnection(AppConfig.ConnectionString))
161+
using (var connection2 = new MySqlConnection(GetIgnoreCommandTransactionConnectionString()))
162+
{
163+
connection1.Open();
164+
connection2.Open();
165+
using (var transaction1 = connection1.BeginTransaction())
166+
using (var command2 = connection2.CreateCommand())
167+
{
168+
command2.Transaction = transaction1;
169+
command2.CommandText = "SELECT 1;";
170+
Assert.Equal(1L, command2.ExecuteScalar());
171+
}
172+
}
173+
}
174+
175+
private static string GetIgnoreCommandTransactionConnectionString()
176+
{
177+
#if BASELINE
178+
return AppConfig.ConnectionString;
179+
#else
180+
return new MySqlConnectionStringBuilder(AppConfig.ConnectionString)
181+
{
182+
IgnoreCommandTransaction = true
183+
}.ConnectionString;
184+
#endif
185+
}
186+
90187
readonly DatabaseFixture m_database;
91188
}
92189
}

tests/SideBySide/Transaction.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Threading.Tasks;
33
using Dapper;
44
using MySql.Data.MySqlClient;
@@ -91,16 +91,6 @@ public void NoCommit()
9191
Assert.Equal(new int[0], results);
9292
}
9393

94-
[Fact]
95-
public void CommandTransactionIsNull()
96-
{
97-
using (var trans = m_connection.BeginTransaction())
98-
using (var cmd = m_connection.CreateCommand())
99-
{
100-
Assert.Null(cmd.Transaction);
101-
}
102-
}
103-
10494
readonly TransactionFixture m_database;
10595
readonly MySqlConnection m_connection;
10696
}

0 commit comments

Comments
 (0)