|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Data.Common;
using System.Data.ProviderBase;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System.Data.OleDb
{
[RequiresDynamicCode(OleDbConnection.TrimWarning)]
public sealed class OleDbTransaction : DbTransaction
{
private readonly OleDbTransaction? _parentTransaction; // strong reference to keep parent alive
private readonly System.Data.IsolationLevel _isolationLevel;
private WeakReference? _nestedTransaction; // child transactions
private WrappedTransaction? _transaction;
internal OleDbConnection _parentConnection;
private sealed class WrappedTransaction : WrappedIUnknown
{
private bool _mustComplete;
internal WrappedTransaction(UnsafeNativeMethods.ITransactionLocal transaction, int isolevel, out OleDbHResult hr) : base(transaction)
{
RuntimeHelpers.PrepareConstrainedRegions();
try
{ }
finally
{
hr = transaction.StartTransaction(isolevel, 0, IntPtr.Zero, out _);
if (0 <= hr)
{
_mustComplete = true;
}
}
}
internal bool MustComplete
{
get { return _mustComplete; }
}
internal OleDbHResult Abort()
{
Debug.Assert(_mustComplete, "transaction already completed");
OleDbHResult hr;
bool mustRelease = false;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
DangerousAddRef(ref mustRelease);
RuntimeHelpers.PrepareConstrainedRegions();
try
{ }
finally
{
hr = (OleDbHResult)NativeOledbWrapper.ITransactionAbort(DangerousGetHandle());
_mustComplete = false;
}
}
finally
{
if (mustRelease)
{
DangerousRelease();
}
}
return hr;
}
internal OleDbHResult Commit()
{
Debug.Assert(_mustComplete, "transaction already completed");
OleDbHResult hr;
bool mustRelease = false;
RuntimeHelpers.PrepareConstrainedRegions();
try
{
DangerousAddRef(ref mustRelease);
RuntimeHelpers.PrepareConstrainedRegions();
try
{ }
finally
{
hr = (OleDbHResult)NativeOledbWrapper.ITransactionCommit(DangerousGetHandle());
if ((0 <= (int)hr) || (OleDbHResult.XACT_E_NOTRANSACTION == hr))
{
_mustComplete = false;
}
}
}
finally
{
if (mustRelease)
{
DangerousRelease();
}
}
return hr;
}
protected override bool ReleaseHandle()
{
if (_mustComplete && (IntPtr.Zero != base.handle))
{
NativeOledbWrapper.ITransactionAbort(base.handle);
_mustComplete = false;
}
return base.ReleaseHandle();
}
}
internal OleDbTransaction(OleDbConnection connection, OleDbTransaction? transaction, IsolationLevel isolevel)
{
_parentConnection = connection;
_parentTransaction = transaction;
switch (isolevel)
{
case IsolationLevel.Unspecified: // OLE DB doesn't support this isolevel on local transactions
isolevel = IsolationLevel.ReadCommitted;
break;
case IsolationLevel.Chaos:
case IsolationLevel.ReadUncommitted:
case IsolationLevel.ReadCommitted:
case IsolationLevel.RepeatableRead:
case IsolationLevel.Serializable:
case IsolationLevel.Snapshot:
break;
default:
throw ADP.InvalidIsolationLevel(isolevel);
}
_isolationLevel = isolevel;
}
public new OleDbConnection? Connection
{
get
{
return _parentConnection;
}
}
protected override DbConnection? DbConnection
{
get
{
return Connection;
}
}
public override IsolationLevel IsolationLevel
{
get
{
if (null == _transaction)
{
throw ADP.TransactionZombied(this);
}
return _isolationLevel;
}
}
internal OleDbTransaction? Parent
{
get
{
return _parentTransaction;
}
}
public OleDbTransaction Begin(IsolationLevel isolevel)
{
if (null == _transaction)
{
throw ADP.TransactionZombied(this);
}
else if ((null != _nestedTransaction) && _nestedTransaction.IsAlive)
{
throw ADP.ParallelTransactionsNotSupported(Connection!);
}
// either the connection will be open or this will be a zombie
OleDbTransaction transaction = new OleDbTransaction(_parentConnection, this, isolevel);
_nestedTransaction = new WeakReference(transaction, false);
UnsafeNativeMethods.ITransactionLocal? wrapper = null;
try
{
wrapper = (UnsafeNativeMethods.ITransactionLocal)_transaction.ComWrapper();
transaction.BeginInternal(wrapper);
}
finally
{
if (null != wrapper)
{
Marshal.ReleaseComObject(wrapper);
}
}
return transaction;
}
public OleDbTransaction Begin()
{
return Begin(IsolationLevel.ReadCommitted);
}
internal void BeginInternal(UnsafeNativeMethods.ITransactionLocal transaction)
{
OleDbHResult hr;
_transaction = new WrappedTransaction(transaction, (int)_isolationLevel, out hr);
if (hr < 0)
{
_transaction.Dispose();
_transaction = null;
ProcessResults(hr);
}
}
public override void Commit()
{
if (null == _transaction)
{
throw ADP.TransactionZombied(this);
}
CommitInternal();
}
private void CommitInternal()
{
if (null == _transaction)
{
return;
}
if (null != _nestedTransaction)
{
OleDbTransaction? transaction = (OleDbTransaction?)_nestedTransaction.Target;
if ((null != transaction) && _nestedTransaction.IsAlive)
{
transaction.CommitInternal();
}
_nestedTransaction = null;
}
OleDbHResult hr = _transaction.Commit();
if (!_transaction.MustComplete)
{
_transaction.Dispose();
_transaction = null;
DisposeManaged();
}
if (hr < 0)
{
// if an exception is thrown, user can try to commit their transaction again
ProcessResults(hr);
}
}
/*public OleDbCommand CreateCommand() {
OleDbCommand cmd = Connection.CreateCommand();
cmd.Transaction = this;
return cmd;
}
IDbCommand IDbTransaction.CreateCommand() {
return CreateCommand();
}*/
protected override void Dispose(bool disposing)
{
if (disposing)
{
DisposeManaged();
RollbackInternal(false);
}
base.Dispose(disposing);
}
private void DisposeManaged()
{
if (null != _parentTransaction)
{
_parentTransaction._nestedTransaction = null;
//_parentTransaction = null;
}
else
{
_parentConnection?.LocalTransaction = null;
}
_parentConnection = null!;
}
private void ProcessResults(OleDbHResult hr)
{
Exception? e = OleDbConnection.ProcessResults(hr, _parentConnection);
if (null != e)
{ throw e; }
}
public override void Rollback()
{
if (null == _transaction)
{
throw ADP.TransactionZombied(this);
}
DisposeManaged();
RollbackInternal(true); // no recover if this throws an exception
}
/*protected virtual*/
internal OleDbHResult RollbackInternal(bool exceptionHandling)
{
OleDbHResult hr = 0;
if (null != _transaction)
{
if (null != _nestedTransaction)
{
OleDbTransaction? transaction = (OleDbTransaction?)_nestedTransaction.Target;
if ((null != transaction) && _nestedTransaction.IsAlive)
{
hr = transaction.RollbackInternal(exceptionHandling);
if (exceptionHandling && (hr < 0))
{
SafeNativeMethods.Wrapper.ClearErrorInfo();
return hr;
}
}
_nestedTransaction = null;
}
hr = _transaction.Abort();
_transaction.Dispose();
_transaction = null;
if (hr < 0)
{
if (exceptionHandling)
{
ProcessResults(hr);
}
else
{
SafeNativeMethods.Wrapper.ClearErrorInfo();
}
}
}
return hr;
}
internal static OleDbTransaction TransactionLast(OleDbTransaction head)
{
if (null != head._nestedTransaction)
{
OleDbTransaction? current = (OleDbTransaction?)head._nestedTransaction.Target;
if ((null != current) && head._nestedTransaction.IsAlive)
{
return TransactionLast(current);
}
}
return head;
}
internal static OleDbTransaction? TransactionUpdate(OleDbTransaction? transaction)
{
if ((null != transaction) && (null == transaction._transaction))
{
return null;
}
return transaction;
}
}
}
|