-
Notifications
You must be signed in to change notification settings - Fork 300
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
Fix | Default SqlCommand's transaction to owning SqlConnection's local transaction #330
Changes from all commits
f97cf86
b8aff14
75596e3
d2a03b2
e476ba4
361c079
bed3b4a
75a5f25
092dbc3
46ef7e5
c79224e
946079e
a1a158c
455c770
afe1233
ab8d7cc
bc89d35
0d2bc21
430058e
d1834d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -261,9 +261,6 @@ private CachedAsyncState cachedAsyncState | |
|
||
private SqlNotificationRequest _notification; | ||
|
||
// transaction support | ||
private SqlTransaction _transaction; | ||
|
||
private StatementCompletedEventHandler _statementCompletedEventHandler; | ||
|
||
private TdsParserStateObject _stateObj; // this is the TDS session we're using. | ||
|
@@ -376,14 +373,6 @@ private SqlCommand(SqlCommand from) : this() | |
} | ||
} | ||
|
||
// Check to see if the currently set transaction has completed. If so, | ||
// null out our local reference. | ||
if (null != _transaction && _transaction.Connection == null) | ||
{ | ||
_transaction = null; | ||
} | ||
|
||
|
||
// Command is no longer prepared on new connection, cleanup prepare status | ||
if (IsPrepared) | ||
{ | ||
|
@@ -467,25 +456,55 @@ internal SqlStatistics Statistics | |
{ | ||
get | ||
{ | ||
// if the transaction object has been zombied, just return null | ||
if ((null != _transaction) && (null == _transaction.Connection)) | ||
{ | ||
_transaction = null; | ||
} | ||
return _transaction; | ||
// there is no real reason to read this value except when creating a new command based on this command | ||
// so we just use the Connection's CurrentTransaction since that it the only valid value anyway | ||
SqlTransaction transaction = (_activeConnection?.InnerConnection as SqlInternalConnectionTds)?.CurrentTransaction?.Parent; | ||
return transaction?.IsZombied == true ? default(SqlTransaction) : transaction; | ||
} | ||
set | ||
{ | ||
// Don't allow the transaction to be changed while in an async operation. | ||
if (_transaction != value && _activeConnection != null) | ||
if (Transaction != value && _activeConnection != null) | ||
{ // If new value... | ||
if (cachedAsyncState.PendingAsyncOperation) | ||
{ // If in pending async state, throw | ||
throw SQL.CannotModifyPropertyAsyncOperationInProgress(); | ||
} | ||
} | ||
|
||
_transaction = value; | ||
// on null transaction just move on | ||
if (value is null) | ||
{ | ||
return; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand that a null value here wouldn't matter as the command will just use the connection's transaction. But since this setter is validating the input, shouldn't null also throw? A user assigning null to this Transaction property will think the command will execute under that transaction, which they believe to be null. |
||
|
||
// on zombied transaction throw and warn the user | ||
// or just fail silently? | ||
if (value.IsZombied) | ||
{ | ||
throw ADP.TransactionZombied(value); | ||
} | ||
|
||
// if connection is not already set-up, grab it from the transaction | ||
if (null == _activeConnection) | ||
{ | ||
Connection = value.Connection; | ||
} | ||
|
||
// if connection differ's from the transaction's connection, throw | ||
if (_activeConnection != value.Connection) | ||
{ | ||
throw ADP.TransactionConnectionMismatch(); | ||
} | ||
|
||
// check to see if transaction | ||
if (value != Transaction) | ||
{ | ||
throw ADP.TransactionConnectionMismatch(); | ||
} | ||
|
||
// we don't store the transaction in any way as the connection has its own value | ||
// all above code are just validations for the user's input | ||
} | ||
} | ||
|
||
|
@@ -4617,21 +4636,6 @@ private void ValidateCommand(bool isAsync, [CallerMemberName] string method = "" | |
// close any non MARS dead readers, if applicable, and then throw if still busy. | ||
// Throw if we have a live reader on this command | ||
_activeConnection.ValidateConnectionForExecute(method, this); | ||
// Check to see if the currently set transaction has completed. If so, | ||
// null out our local reference. | ||
if (null != _transaction && _transaction.Connection == null) | ||
_transaction = null; | ||
|
||
// throw if the connection is in a transaction but there is no | ||
// locally assigned transaction object | ||
if (_activeConnection.HasLocalTransactionFromAPI && (null == _transaction)) | ||
throw ADP.TransactionRequired(method); | ||
|
||
// if we have a transaction, check to ensure that the active | ||
// connection property matches the connection associated with | ||
// the transaction | ||
if (null != _transaction && _activeConnection != _transaction.Connection) | ||
throw ADP.TransactionConnectionMismatch(); | ||
|
||
if (string.IsNullOrEmpty(this.CommandText)) | ||
throw ADP.CommandTextRequired(method); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Behavior change. I have a command lying around and the transaction was changed for the command. Now I associate the new connection with it, and it magically overwrote previous transaction.
Can we stick to the lowest common denominator on how this convenience of deriving the transaction for this command is going to work, while preserving most of the old behavior ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since a transaction's lifetime is
BeginTransaction - Commit/Rollback/throw
, and a single connection has a single active transaction, how can a command be lying around with a different transaction that magically changes? If it had a different transaction reference associated, and the connection ran another transaction, then it's a complete transaction which used to be nullified internally in the old functionality too.If you associate a new connection to it, you don't do it in the hopes of keeping the old transaction on the
.Transaction
property and a different connection in the.Connection
. This would not have passed validation previously also.A few lines of code explaining this behavior would be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@saurabh500
Could you provide a few lines of code to verify how these code changes would break?