2016-08-03 10:59:49 +00:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// <copyright file="SqlTransaction.cs" company="Microsoft">
|
|
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
// </copyright>
|
2017-08-21 15:34:15 +00:00
|
|
|
// <owner current="true" primary="true">Microsoft</owner>
|
|
|
|
// <owner current="true" primary="false">Microsoft</owner>
|
2016-08-03 10:59:49 +00:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
namespace System.Data.SqlClient {
|
|
|
|
using System.Data;
|
|
|
|
using System.Data.Common;
|
|
|
|
using System.Data.ProviderBase;
|
|
|
|
using System.Data.Sql;
|
|
|
|
using System.Data.SqlTypes;
|
|
|
|
using System.Diagnostics;
|
|
|
|
using System.Runtime.CompilerServices;
|
|
|
|
using System.Runtime.ConstrainedExecution;
|
|
|
|
using System.Threading;
|
|
|
|
|
|
|
|
public sealed class SqlTransaction : DbTransaction {
|
|
|
|
private static int _objectTypeCount; // Bid counter
|
|
|
|
internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
|
|
|
|
internal readonly IsolationLevel _isolationLevel = IsolationLevel.ReadCommitted;
|
|
|
|
|
|
|
|
private SqlInternalTransaction _internalTransaction;
|
|
|
|
private SqlConnection _connection;
|
|
|
|
|
|
|
|
private bool _isFromAPI;
|
|
|
|
|
|
|
|
internal SqlTransaction(SqlInternalConnection internalConnection, SqlConnection con,
|
|
|
|
IsolationLevel iso, SqlInternalTransaction internalTransaction) {
|
|
|
|
SqlConnection.VerifyExecutePermission();
|
|
|
|
|
|
|
|
_isolationLevel = iso;
|
|
|
|
_connection = con;
|
|
|
|
|
|
|
|
if (internalTransaction == null) {
|
|
|
|
_internalTransaction = new SqlInternalTransaction(internalConnection, TransactionType.LocalFromAPI, this);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
Debug.Assert(internalConnection.CurrentTransaction == internalTransaction, "Unexpected Parser.CurrentTransaction state!");
|
|
|
|
_internalTransaction = internalTransaction;
|
|
|
|
_internalTransaction.InitParent(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// PROPERTIES
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
new public SqlConnection Connection { // MDAC 66655
|
|
|
|
get {
|
|
|
|
if (IsZombied) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return _connection;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override protected DbConnection DbConnection {
|
|
|
|
get {
|
|
|
|
return Connection;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal SqlInternalTransaction InternalTransaction {
|
|
|
|
get {
|
|
|
|
return _internalTransaction;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override public IsolationLevel IsolationLevel {
|
|
|
|
get {
|
|
|
|
ZombieCheck();
|
|
|
|
return _isolationLevel;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private bool IsYukonPartialZombie {
|
|
|
|
get {
|
|
|
|
return (null != _internalTransaction && _internalTransaction.IsCompleted);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal bool IsZombied {
|
|
|
|
get {
|
|
|
|
return (null == _internalTransaction || _internalTransaction.IsCompleted);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal int ObjectID {
|
|
|
|
get {
|
|
|
|
return _objectID;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal SqlStatistics Statistics {
|
|
|
|
get {
|
|
|
|
if (null != _connection) {
|
|
|
|
if (_connection.StatisticsEnabled) {
|
|
|
|
return _connection.Statistics;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// PUBLIC METHODS
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
override public void Commit() {
|
|
|
|
SqlConnection.ExecutePermission.Demand(); // MDAC 81476
|
|
|
|
|
|
|
|
ZombieCheck();
|
|
|
|
|
|
|
|
SqlStatistics statistics = null;
|
|
|
|
IntPtr hscp;
|
|
|
|
|
|
|
|
Bid.ScopeEnter(out hscp, "<sc.SqlTransaction.Commit|API> %d#", ObjectID);
|
|
|
|
Bid.CorrelationTrace("<sc.SqlTransaction.Commit|API|Correlation> ObjectID%d#, ActivityID %ls", ObjectID);
|
|
|
|
|
|
|
|
TdsParser bestEffortCleanupTarget = null;
|
|
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
|
|
try {
|
|
|
|
#if DEBUG
|
|
|
|
TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
|
|
|
|
|
|
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
|
|
try {
|
|
|
|
tdsReliabilitySection.Start();
|
|
|
|
#else
|
|
|
|
{
|
|
|
|
#endif //DEBUG
|
|
|
|
bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
|
|
|
|
statistics = SqlStatistics.StartTimer(Statistics);
|
|
|
|
|
|
|
|
_isFromAPI = true;
|
|
|
|
|
|
|
|
_internalTransaction.Commit();
|
|
|
|
}
|
|
|
|
#if DEBUG
|
|
|
|
finally {
|
|
|
|
tdsReliabilitySection.Stop();
|
|
|
|
}
|
|
|
|
#endif //DEBUG
|
|
|
|
}
|
|
|
|
catch (System.OutOfMemoryException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch (System.StackOverflowException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch (System.Threading.ThreadAbortException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
_isFromAPI = false;
|
|
|
|
|
|
|
|
SqlStatistics.StopTimer(statistics);
|
|
|
|
Bid.ScopeLeave(ref hscp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void Dispose(bool disposing) {
|
|
|
|
if (disposing) {
|
|
|
|
TdsParser bestEffortCleanupTarget = null;
|
|
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
|
|
try {
|
|
|
|
#if DEBUG
|
|
|
|
TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
|
|
|
|
|
|
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
|
|
try {
|
|
|
|
tdsReliabilitySection.Start();
|
|
|
|
#else
|
|
|
|
{
|
|
|
|
#endif //DEBUG
|
|
|
|
bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
|
|
|
|
if (!IsZombied && !IsYukonPartialZombie) {
|
|
|
|
_internalTransaction.Dispose();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#if DEBUG
|
|
|
|
finally {
|
|
|
|
tdsReliabilitySection.Stop();
|
|
|
|
}
|
|
|
|
#endif //DEBUG
|
|
|
|
}
|
|
|
|
catch (System.OutOfMemoryException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch (System.StackOverflowException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch (System.Threading.ThreadAbortException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
|
|
}
|
|
|
|
|
|
|
|
override public void Rollback() {
|
|
|
|
if (IsYukonPartialZombie) {
|
|
|
|
// Put something in the trace in case a customer has an issue
|
|
|
|
if (Bid.AdvancedOn) {
|
|
|
|
Bid.Trace("<sc.SqlTransaction.Rollback|ADV> %d# partial zombie no rollback required\n", ObjectID);
|
|
|
|
}
|
|
|
|
_internalTransaction = null; // yukon zombification
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ZombieCheck();
|
|
|
|
|
|
|
|
SqlStatistics statistics = null;
|
|
|
|
IntPtr hscp;
|
|
|
|
Bid.ScopeEnter(out hscp, "<sc.SqlTransaction.Rollback|API> %d#", ObjectID);
|
|
|
|
Bid.CorrelationTrace("<sc.SqlTransaction.Rollback|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
|
|
|
|
|
|
|
|
TdsParser bestEffortCleanupTarget = null;
|
|
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
|
|
try {
|
|
|
|
#if DEBUG
|
|
|
|
TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
|
|
|
|
|
|
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
|
|
try {
|
|
|
|
tdsReliabilitySection.Start();
|
|
|
|
#else
|
|
|
|
{
|
|
|
|
#endif //DEBUG
|
|
|
|
bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
|
|
|
|
statistics = SqlStatistics.StartTimer(Statistics);
|
|
|
|
|
|
|
|
_isFromAPI = true;
|
|
|
|
|
|
|
|
_internalTransaction.Rollback();
|
|
|
|
}
|
|
|
|
#if DEBUG
|
|
|
|
finally {
|
|
|
|
tdsReliabilitySection.Stop();
|
|
|
|
}
|
|
|
|
#endif //DEBUG
|
|
|
|
}
|
|
|
|
catch (System.OutOfMemoryException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch (System.StackOverflowException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch (System.Threading.ThreadAbortException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
_isFromAPI = false;
|
|
|
|
|
|
|
|
SqlStatistics.StopTimer(statistics);
|
|
|
|
Bid.ScopeLeave(ref hscp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Rollback(string transactionName) {
|
|
|
|
SqlConnection.ExecutePermission.Demand(); // MDAC 81476
|
|
|
|
|
|
|
|
ZombieCheck();
|
|
|
|
|
|
|
|
SqlStatistics statistics = null;
|
|
|
|
IntPtr hscp;
|
|
|
|
Bid.ScopeEnter(out hscp, "<sc.SqlTransaction.Rollback|API> %d# transactionName='%ls'", ObjectID, transactionName);
|
|
|
|
|
|
|
|
TdsParser bestEffortCleanupTarget = null;
|
|
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
|
|
try {
|
|
|
|
#if DEBUG
|
|
|
|
TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
|
|
|
|
|
|
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
|
|
try {
|
|
|
|
tdsReliabilitySection.Start();
|
|
|
|
#else
|
|
|
|
{
|
|
|
|
#endif //DEBUG
|
|
|
|
bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
|
|
|
|
statistics = SqlStatistics.StartTimer(Statistics);
|
|
|
|
|
|
|
|
_isFromAPI = true;
|
|
|
|
|
|
|
|
_internalTransaction.Rollback(transactionName);
|
|
|
|
}
|
|
|
|
#if DEBUG
|
|
|
|
finally {
|
|
|
|
tdsReliabilitySection.Stop();
|
|
|
|
}
|
|
|
|
#endif //DEBUG
|
|
|
|
}
|
|
|
|
catch (System.OutOfMemoryException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch (System.StackOverflowException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch (System.Threading.ThreadAbortException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
_isFromAPI = false;
|
|
|
|
|
|
|
|
SqlStatistics.StopTimer(statistics);
|
|
|
|
Bid.ScopeLeave(ref hscp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Save(string savePointName) {
|
|
|
|
SqlConnection.ExecutePermission.Demand(); // MDAC 81476
|
|
|
|
|
|
|
|
ZombieCheck();
|
|
|
|
|
|
|
|
SqlStatistics statistics = null;
|
|
|
|
IntPtr hscp;
|
|
|
|
Bid.ScopeEnter(out hscp, "<sc.SqlTransaction.Save|API> %d# savePointName='%ls'", ObjectID, savePointName);
|
|
|
|
|
|
|
|
TdsParser bestEffortCleanupTarget = null;
|
|
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
|
|
try {
|
|
|
|
#if DEBUG
|
|
|
|
TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
|
|
|
|
|
|
|
|
RuntimeHelpers.PrepareConstrainedRegions();
|
|
|
|
try {
|
|
|
|
tdsReliabilitySection.Start();
|
|
|
|
#else
|
|
|
|
{
|
|
|
|
#endif //DEBUG
|
|
|
|
bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection);
|
|
|
|
statistics = SqlStatistics.StartTimer(Statistics);
|
|
|
|
|
|
|
|
_internalTransaction.Save(savePointName);
|
|
|
|
}
|
|
|
|
#if DEBUG
|
|
|
|
finally {
|
|
|
|
tdsReliabilitySection.Stop();
|
|
|
|
}
|
|
|
|
#endif //DEBUG
|
|
|
|
}
|
|
|
|
catch (System.OutOfMemoryException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch (System.StackOverflowException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
catch (System.Threading.ThreadAbortException e) {
|
|
|
|
_connection.Abort(e);
|
|
|
|
SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget);
|
|
|
|
throw;
|
|
|
|
}
|
|
|
|
finally {
|
|
|
|
SqlStatistics.StopTimer(statistics);
|
|
|
|
Bid.ScopeLeave(ref hscp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// INTERNAL METHODS
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
internal void Zombie() {
|
|
|
|
// SQLBUDT #402544 For Yukon, we have to defer "zombification" until
|
|
|
|
// we get past the users' next rollback, else we'll
|
|
|
|
// throw an exception there that is a breaking change.
|
|
|
|
// Of course, if the connection is aready closed,
|
|
|
|
// then we're free to zombify...
|
|
|
|
SqlInternalConnection internalConnection = (_connection.InnerConnection as SqlInternalConnection);
|
|
|
|
if (null != internalConnection && internalConnection.IsYukonOrNewer && !_isFromAPI) {
|
|
|
|
if (Bid.AdvancedOn) {
|
|
|
|
Bid.Trace("<sc.SqlTransaction.Zombie|ADV> %d# yukon deferred zombie\n", ObjectID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
_internalTransaction = null; // pre-yukon zombification
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// PRIVATE METHODS
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
private void ZombieCheck() {
|
|
|
|
// If this transaction has been completed, throw exception since it is unusable.
|
|
|
|
if (IsZombied) {
|
|
|
|
|
|
|
|
if (IsYukonPartialZombie) {
|
|
|
|
_internalTransaction = null; // yukon zombification
|
|
|
|
}
|
|
|
|
|
|
|
|
throw ADP.TransactionZombied(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|