1 //------------------------------------------------------------------------------
2 // <copyright file="OleDbTransaction.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
8 
9 namespace System.Data.OleDb {
10 
11     using System.Data;
12     using System.Data.Common;
13     using System.Data.ProviderBase;
14     using System.Diagnostics;
15     using System.Runtime.CompilerServices;
16     using System.Runtime.ConstrainedExecution;
17     using System.Runtime.InteropServices;
18     using System.Threading;
19 
20     public sealed class OleDbTransaction : DbTransaction {
21 
22         private readonly OleDbTransaction _parentTransaction; // strong reference to keep parent alive
23         private readonly System.Data.IsolationLevel _isolationLevel;
24 
25         private WeakReference _nestedTransaction; // child transactions
26         private WrappedTransaction _transaction;
27 
28         internal OleDbConnection _parentConnection;
29 
30         private static int _objectTypeCount; // Bid counter
31         internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount);
32 
33         private sealed class WrappedTransaction : WrappedIUnknown {
34 
35             private bool _mustComplete;
36 
WrappedTransaction(UnsafeNativeMethods.ITransactionLocal transaction, int isolevel, out OleDbHResult hr)37             internal WrappedTransaction(UnsafeNativeMethods.ITransactionLocal transaction, int isolevel, out OleDbHResult hr) : base(transaction) {
38                 int transactionLevel = 0;
39                 Bid.Trace("<oledb.ITransactionLocal.StartTransaction|API|OLEDB>\n");
40                 RuntimeHelpers.PrepareConstrainedRegions();
41                 try { } finally {
42                     hr = transaction.StartTransaction(isolevel, 0, IntPtr.Zero, out transactionLevel);
43                     if (0 <= hr) {
44                         _mustComplete = true;
45                     }
46                 }
47                 Bid.Trace("<oledb.ITransactionLocal.StartTransaction|API|OLEDB|RET> %08X{HRESULT}\n", hr);
48             }
49 
50             internal bool MustComplete {
51                 get { return _mustComplete; }
52             }
53 
Abort()54             internal OleDbHResult Abort() {
55                 Debug.Assert(_mustComplete, "transaction already completed");
56                 OleDbHResult hr;
57                 bool mustRelease = false;
58                 RuntimeHelpers.PrepareConstrainedRegions();
59                 try {
60                     DangerousAddRef(ref mustRelease);
61 
62                     Bid.Trace("<oledb.ITransactionLocal.Abort|API|OLEDB> handle=%p\n", base.handle);
63                     RuntimeHelpers.PrepareConstrainedRegions();
64                     try { } finally {
65                         hr = (OleDbHResult)NativeOledbWrapper.ITransactionAbort(DangerousGetHandle());
66                         _mustComplete = false;
67                     }
68                     Bid.Trace("<oledb.ITransactionLocal.Abort|API|OLEDB|RET> %08X{HRESULT}\n", hr);
69                 }
70                 finally {
71                     if (mustRelease) {
72                         DangerousRelease();
73                     }
74                 }
75                 return hr;
76             }
77 
Commit()78             internal OleDbHResult Commit() {
79                 Debug.Assert(_mustComplete, "transaction already completed");
80                 OleDbHResult hr;
81                 bool mustRelease = false;
82                 RuntimeHelpers.PrepareConstrainedRegions();
83                 try {
84                     DangerousAddRef(ref mustRelease);
85 
86                     Bid.Trace("<oledb.ITransactionLocal.Commit|API|OLEDB> handle=%p\n", base.handle);
87                     RuntimeHelpers.PrepareConstrainedRegions();
88                     try { } finally {
89                         hr = (OleDbHResult)NativeOledbWrapper.ITransactionCommit(DangerousGetHandle());
90                         if ((0 <= (int)hr) || (OleDbHResult.XACT_E_NOTRANSACTION == hr)) {
91                             _mustComplete = false;
92                         }
93                     }
94                     Bid.Trace("<oledb.ITransactionLocal.Commit|API|OLEDB|RET> %08X{HRESULT}\n", hr);
95                 }
96                 finally {
97                     if (mustRelease) {
98                         DangerousRelease();
99                     }
100                 }
101                 return hr;
102             }
103 
ReleaseHandle()104             override protected bool ReleaseHandle() {
105                 if (_mustComplete && (IntPtr.Zero != base.handle)) {
106                     Bid.Trace("<oledb.ITransactionLocal.Abort|API|OLEDB|INFO> handle=%p\n", base.handle);
107                     OleDbHResult hr = (OleDbHResult)NativeOledbWrapper.ITransactionAbort(base.handle);
108                     _mustComplete = false;
109                     Bid.Trace("<oledb.ITransactionLocal.Abort|API|OLEDB|INFO|RET> %08X{HRESULT}\n", hr);
110                 }
111                 return base.ReleaseHandle();
112             }
113         }
114 
OleDbTransaction(OleDbConnection connection, OleDbTransaction transaction, IsolationLevel isolevel)115         internal OleDbTransaction(OleDbConnection connection, OleDbTransaction transaction, IsolationLevel isolevel) {
116             OleDbConnection.VerifyExecutePermission();
117 
118             _parentConnection = connection;
119             _parentTransaction = transaction;
120 
121             switch(isolevel) {
122             case IsolationLevel.Unspecified: // OLE DB doesn't support this isolevel on local transactions
123                 isolevel = IsolationLevel.ReadCommitted;
124                 break;
125             case IsolationLevel.Chaos:
126             case IsolationLevel.ReadUncommitted:
127             case IsolationLevel.ReadCommitted:
128             case IsolationLevel.RepeatableRead:
129             case IsolationLevel.Serializable:
130             case IsolationLevel.Snapshot:
131                 break;
132             default:
133                 throw ADP.InvalidIsolationLevel(isolevel); // MDAC 74269
134             }
135             _isolationLevel = isolevel;
136         }
137 
138         new public OleDbConnection Connection { // MDAC 66655
139             get {
140                 return _parentConnection;
141             }
142         }
143 
144         override protected DbConnection DbConnection {
145             get {
146                 return Connection;
147             }
148         }
149 
150         override public IsolationLevel IsolationLevel {
151             get {
152                 if (null == _transaction) {
153                     throw ADP.TransactionZombied(this);
154                 }
155                 return _isolationLevel;
156             }
157         }
158 
159         internal int ObjectID {
160             get {
161                 return _objectID;
162             }
163         }
164 
165         internal OleDbTransaction Parent {
166             get {
167                 return _parentTransaction;
168             }
169         }
170 
Begin(IsolationLevel isolevel)171         public OleDbTransaction Begin(IsolationLevel isolevel) {
172             OleDbConnection.ExecutePermission.Demand(); // MDAC 81476
173 
174             IntPtr hscp;
175             Bid.ScopeEnter(out hscp, "<oledb.OleDbTransaction.Begin|API> %d#, isolevel=%d{IsolationLevel}", ObjectID, (int)isolevel);
176             try {
177                 if (null == _transaction) {
178                     throw ADP.TransactionZombied(this);
179                 }
180                 else if ((null != _nestedTransaction) && _nestedTransaction.IsAlive) {
181                     throw ADP.ParallelTransactionsNotSupported(Connection);
182                 }
183                 // either the connection will be open or this will be a zombie
184 
185                 OleDbTransaction transaction = new OleDbTransaction(_parentConnection, this, isolevel);
186                 _nestedTransaction = new WeakReference(transaction, false);
187 
188                 UnsafeNativeMethods.ITransactionLocal wrapper = null;
189                 try {
190                     wrapper = (UnsafeNativeMethods.ITransactionLocal)_transaction.ComWrapper();
191                     transaction.BeginInternal(wrapper);
192                 }
193                 finally {
194                     if (null != wrapper) {
195                         Marshal.ReleaseComObject(wrapper);
196                     }
197                 }
198                 return transaction;
199             }
200             finally {
201                 Bid.ScopeLeave(ref hscp);
202             }
203         }
204 
Begin()205         public OleDbTransaction Begin() {
206             return Begin(IsolationLevel.ReadCommitted);
207         }
208 
BeginInternal(UnsafeNativeMethods.ITransactionLocal transaction)209         internal void BeginInternal(UnsafeNativeMethods.ITransactionLocal transaction) {
210             OleDbHResult hr;
211             _transaction = new WrappedTransaction(transaction, (int) _isolationLevel, out hr);
212             if (hr < 0) {
213                 _transaction.Dispose();
214                 _transaction = null;
215                 ProcessResults(hr);
216             }
217         }
218 
Commit()219         override public void Commit() {
220             OleDbConnection.ExecutePermission.Demand(); // MDAC 81476
221 
222             IntPtr hscp;
223             Bid.ScopeEnter(out hscp, "<oledb.OleDbTransaction.Commit|API> %d#", ObjectID);
224             try {
225                 if (null == _transaction) {
226                     throw ADP.TransactionZombied(this);
227                 }
228                 CommitInternal();
229             }
230             finally {
231                 Bid.ScopeLeave(ref hscp);
232             }
233         }
234 
CommitInternal()235         private void CommitInternal() {
236             if (null == _transaction) {
237                 return;
238             }
239             if (null != _nestedTransaction) {
240                 OleDbTransaction transaction = (OleDbTransaction) _nestedTransaction.Target;
241                 if ((null != transaction) && _nestedTransaction.IsAlive) {
242                     transaction.CommitInternal();
243                 }
244                 _nestedTransaction = null;
245             }
246             OleDbHResult hr = _transaction.Commit();
247             if (!_transaction.MustComplete) {
248                 _transaction.Dispose();
249                 _transaction = null;
250 
251                 DisposeManaged();
252             }
253             if (hr < 0) {
254                 // if an exception is thrown, user can try to commit their transaction again
255                 ProcessResults(hr);
256             }
257         }
258 
259         /*public OleDbCommand CreateCommand() { // MDAC 68309
260             OleDbCommand cmd = Connection.CreateCommand();
261             cmd.Transaction = this;
262             return cmd;
263         }
264 
265         IDbCommand IDbTransaction.CreateCommand() {
266             return CreateCommand();
267         }*/
268 
Dispose(bool disposing)269         protected override void Dispose(bool disposing) {
270             if (disposing) {
271                 DisposeManaged();
272                 RollbackInternal(false);
273             }
274             base.Dispose(disposing);
275         }
276 
DisposeManaged()277         private void DisposeManaged() {
278             if (null != _parentTransaction) {
279                 _parentTransaction._nestedTransaction = null;
280                 //_parentTransaction = null;
281             }
282             else if (null != _parentConnection) { // MDAC 67287
283                 _parentConnection.LocalTransaction = null;
284             }
285             _parentConnection = null;
286         }
287 
ProcessResults(OleDbHResult hr)288         private void ProcessResults(OleDbHResult hr) {
289             Exception e = OleDbConnection.ProcessResults(hr, _parentConnection, this);
290             if (null != e) { throw e; }
291         }
292 
Rollback()293         override public void Rollback() {
294             IntPtr hscp;
295             Bid.ScopeEnter(out hscp, "<oledb.OleDbTransaction.Rollback|API> %d#", ObjectID);
296             try {
297                 if (null == _transaction) {
298                     throw ADP.TransactionZombied(this);
299                 }
300                 DisposeManaged();
301                 RollbackInternal(true); // no recover if this throws an exception
302             }
303             finally {
304                 Bid.ScopeLeave(ref hscp);
305             }
306         }
307 
RollbackInternal(bool exceptionHandling)308         /*protected virtual*/internal OleDbHResult RollbackInternal(bool exceptionHandling) {
309             OleDbHResult hr = 0;
310             if (null != _transaction) {
311                 if (null != _nestedTransaction) {
312                     OleDbTransaction transaction = (OleDbTransaction) _nestedTransaction.Target;
313                     if ((null != transaction) && _nestedTransaction.IsAlive) {
314                         hr = transaction.RollbackInternal(exceptionHandling);
315                         if (exceptionHandling && (hr < 0)) {
316                             SafeNativeMethods.Wrapper.ClearErrorInfo();
317                             return hr;
318                         }
319                     }
320                     _nestedTransaction = null;
321                 }
322                 hr = _transaction.Abort();
323                 _transaction.Dispose();
324                 _transaction = null;
325                 if (hr < 0) {
326                     if (exceptionHandling) {
327                         ProcessResults(hr);
328                     }
329                     else {
330                         SafeNativeMethods.Wrapper.ClearErrorInfo();
331                     }
332                 }
333             }
334             return hr;
335         }
336 
TransactionLast(OleDbTransaction head)337         static internal OleDbTransaction TransactionLast(OleDbTransaction head) {
338             if (null != head._nestedTransaction) {
339                 OleDbTransaction current = (OleDbTransaction) head._nestedTransaction.Target;
340                 if ((null != current) && head._nestedTransaction.IsAlive) {
341                     return TransactionLast(current);
342                 }
343             }
344             return head;
345         }
346 
TransactionUpdate(OleDbTransaction transaction)347         static internal OleDbTransaction TransactionUpdate(OleDbTransaction transaction) {
348             if ((null != transaction) && (null == transaction._transaction)) {
349                 return null;
350             }
351             return transaction;
352         }
353     }
354 }
355