/src/NHibernate/Transaction/AdoTransaction.cs

http://github.com/nhibernate/nhibernate-core · C# · 491 lines · 339 code · 51 blank · 101 comment · 46 complexity · 49dbe715de2f7796c1944356a87ed8f5 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Data.Common;
  5. using NHibernate.Driver;
  6. using NHibernate.Engine;
  7. using NHibernate.Impl;
  8. namespace NHibernate.Transaction
  9. {
  10. /// <summary>
  11. /// Wraps an ADO.NET <see cref="DbTransaction"/> to implement
  12. /// the <see cref="ITransaction" /> interface.
  13. /// </summary>
  14. public partial class AdoTransaction : ITransaction
  15. {
  16. private static readonly INHibernateLogger log = NHibernateLogger.For(typeof(AdoTransaction));
  17. private ISessionImplementor session;
  18. private DbTransaction trans;
  19. private bool begun;
  20. private bool committed;
  21. private bool rolledBack;
  22. private bool commitFailed;
  23. // Since v5.2
  24. [Obsolete]
  25. private List<ISynchronization> synchronizations;
  26. private List<ITransactionCompletionSynchronization> _completionSynchronizations;
  27. /// <summary>
  28. /// Initializes a new instance of the <see cref="AdoTransaction"/> class.
  29. /// </summary>
  30. /// <param name="session">The <see cref="ISessionImplementor"/> the Transaction is for.</param>
  31. public AdoTransaction(ISessionImplementor session)
  32. {
  33. this.session = session;
  34. sessionId = this.session.SessionId;
  35. }
  36. /// <summary>
  37. /// Enlist the <see cref="DbCommand"/> in the current <see cref="ITransaction"/>.
  38. /// </summary>
  39. /// <param name="command">The <see cref="DbCommand"/> to enlist in this Transaction.</param>
  40. /// <remarks>
  41. /// <para>
  42. /// This takes care of making sure the <see cref="DbCommand"/>'s Transaction property
  43. /// contains the correct <see cref="DbTransaction"/> or <see langword="null" /> if there is no
  44. /// Transaction for the ISession - ie <c>BeginTransaction()</c> not called.
  45. /// </para>
  46. /// <para>
  47. /// This method may be called even when the transaction is disposed.
  48. /// </para>
  49. /// </remarks>
  50. public void Enlist(DbCommand command)
  51. {
  52. if (trans == null)
  53. {
  54. if (log.IsWarnEnabled())
  55. {
  56. if (command.Transaction != null)
  57. {
  58. log.Warn("set a nonnull DbCommand.Transaction to null because the Session had no Transaction");
  59. }
  60. }
  61. command.Transaction = null;
  62. return;
  63. }
  64. else
  65. {
  66. if (log.IsWarnEnabled())
  67. {
  68. // got into here because the command was being initialized and had a null Transaction - probably
  69. // don't need to be confused by that - just a normal part of initialization...
  70. if (command.Transaction != null && command.Transaction != trans)
  71. {
  72. log.Warn("The DbCommand had a different Transaction than the Session. This can occur when " +
  73. "Disconnecting and Reconnecting Sessions because the PreparedCommand Cache is Session specific.");
  74. }
  75. }
  76. log.Debug("Enlist Command");
  77. // If you try to assign a disposed transaction to a command with MSSQL, it will leave the command's
  78. // transaction as null and not throw an error. With SQLite, for example, it will throw an exception
  79. // here instead. Because of this, we set the trans field to null in when Dispose is called.
  80. command.Transaction = trans;
  81. }
  82. }
  83. // Since 5.2
  84. [Obsolete("Use RegisterSynchronization(ITransactionCompletionSynchronization) instead")]
  85. public void RegisterSynchronization(ISynchronization sync)
  86. {
  87. if (sync == null) throw new ArgumentNullException("sync");
  88. if (synchronizations == null)
  89. {
  90. synchronizations = new List<ISynchronization>();
  91. }
  92. synchronizations.Add(sync);
  93. }
  94. public void RegisterSynchronization(ITransactionCompletionSynchronization synchronization)
  95. {
  96. if (synchronization == null)
  97. throw new ArgumentNullException(nameof(synchronization));
  98. // It is tempting to use the session ActionQueue instead, but stateless sessions do not have one.
  99. if (_completionSynchronizations == null)
  100. {
  101. _completionSynchronizations = new List<ITransactionCompletionSynchronization>();
  102. }
  103. _completionSynchronizations.Add(synchronization);
  104. }
  105. public void Begin()
  106. {
  107. Begin(IsolationLevel.Unspecified);
  108. }
  109. /// <summary>
  110. /// Begins the <see cref="DbTransaction"/> on the <see cref="DbConnection"/>
  111. /// used by the <see cref="ISession"/>.
  112. /// </summary>
  113. /// <exception cref="TransactionException">
  114. /// Thrown if there is any problems encountered while trying to create
  115. /// the <see cref="DbTransaction"/>.
  116. /// </exception>
  117. public void Begin(IsolationLevel isolationLevel)
  118. {
  119. using (session.BeginProcess())
  120. {
  121. if (begun)
  122. {
  123. return;
  124. }
  125. if (commitFailed)
  126. {
  127. throw new TransactionException("Cannot restart transaction after failed commit");
  128. }
  129. if (isolationLevel == IsolationLevel.Unspecified)
  130. {
  131. isolationLevel = session.Factory.Settings.IsolationLevel;
  132. }
  133. log.Debug("Begin ({0})", isolationLevel);
  134. try
  135. {
  136. trans = session.Factory.ConnectionProvider.Driver.BeginTransaction(isolationLevel, session.Connection);
  137. }
  138. catch (HibernateException)
  139. {
  140. // Don't wrap HibernateExceptions
  141. throw;
  142. }
  143. catch (Exception e)
  144. {
  145. log.Error(e, "Begin transaction failed");
  146. throw new TransactionException("Begin failed with SQL exception", e);
  147. }
  148. begun = true;
  149. committed = false;
  150. rolledBack = false;
  151. session.AfterTransactionBegin(this);
  152. foreach (var dependentSession in session.ConnectionManager.DependentSessions)
  153. dependentSession.AfterTransactionBegin(this);
  154. }
  155. }
  156. private void AfterTransactionCompletion(bool successful)
  157. {
  158. session.ConnectionManager.AfterTransaction();
  159. session.AfterTransactionCompletion(successful, this);
  160. NotifyLocalSynchsAfterTransactionCompletion(successful);
  161. foreach (var dependentSession in session.ConnectionManager.DependentSessions)
  162. dependentSession.AfterTransactionCompletion(successful, this);
  163. session = null;
  164. begun = false;
  165. }
  166. /// <summary>
  167. /// Commits the <see cref="ITransaction"/> by flushing the <see cref="ISession"/>
  168. /// and committing the <see cref="DbTransaction"/>.
  169. /// </summary>
  170. /// <exception cref="TransactionException">
  171. /// Thrown if there is any exception while trying to call <c>Commit()</c> on
  172. /// the underlying <see cref="DbTransaction"/>.
  173. /// </exception>
  174. public void Commit()
  175. {
  176. using (session.BeginProcess())
  177. {
  178. CheckNotDisposed();
  179. CheckBegun();
  180. CheckNotZombied();
  181. log.Debug("Start Commit");
  182. session.BeforeTransactionCompletion(this);
  183. NotifyLocalSynchsBeforeTransactionCompletion();
  184. foreach (var dependentSession in session.ConnectionManager.DependentSessions)
  185. dependentSession.BeforeTransactionCompletion(this);
  186. try
  187. {
  188. trans.Commit();
  189. log.Debug("DbTransaction Committed");
  190. committed = true;
  191. AfterTransactionCompletion(true);
  192. Dispose();
  193. }
  194. catch (HibernateException e)
  195. {
  196. log.Error(e, "Commit failed");
  197. AfterTransactionCompletion(false);
  198. commitFailed = true;
  199. // Don't wrap HibernateExceptions
  200. throw;
  201. }
  202. catch (Exception e)
  203. {
  204. log.Error(e, "Commit failed");
  205. AfterTransactionCompletion(false);
  206. commitFailed = true;
  207. throw new TransactionException("Commit failed with SQL exception", e);
  208. }
  209. finally
  210. {
  211. CloseIfRequired();
  212. }
  213. }
  214. }
  215. /// <summary>
  216. /// Rolls back the <see cref="ITransaction"/> by calling the method <c>Rollback</c>
  217. /// on the underlying <see cref="DbTransaction"/>.
  218. /// </summary>
  219. /// <exception cref="TransactionException">
  220. /// Thrown if there is any exception while trying to call <c>Rollback()</c> on
  221. /// the underlying <see cref="DbTransaction"/>.
  222. /// </exception>
  223. public void Rollback()
  224. {
  225. using (SessionIdLoggingContext.CreateOrNull(sessionId))
  226. {
  227. CheckNotDisposed();
  228. CheckBegun();
  229. CheckNotZombied();
  230. log.Debug("Rollback");
  231. if (!commitFailed)
  232. {
  233. try
  234. {
  235. trans.Rollback();
  236. log.Debug("DbTransaction RolledBack");
  237. rolledBack = true;
  238. Dispose();
  239. }
  240. catch (HibernateException e)
  241. {
  242. log.Error(e, "Rollback failed");
  243. // Don't wrap HibernateExceptions
  244. throw;
  245. }
  246. catch (Exception e)
  247. {
  248. log.Error(e, "Rollback failed");
  249. throw new TransactionException("Rollback failed with SQL Exception", e);
  250. }
  251. finally
  252. {
  253. AfterTransactionCompletion(false);
  254. CloseIfRequired();
  255. }
  256. }
  257. }
  258. }
  259. /// <summary>
  260. /// Gets a <see cref="Boolean"/> indicating if the transaction was rolled back.
  261. /// </summary>
  262. /// <value>
  263. /// <see langword="true" /> if the <see cref="DbTransaction"/> had <c>Rollback</c> called
  264. /// without any exceptions.
  265. /// </value>
  266. public bool WasRolledBack
  267. {
  268. get { return rolledBack; }
  269. }
  270. /// <summary>
  271. /// Gets a <see cref="Boolean"/> indicating if the transaction was committed.
  272. /// </summary>
  273. /// <value>
  274. /// <see langword="true" /> if the <see cref="DbTransaction"/> had <c>Commit</c> called
  275. /// without any exceptions.
  276. /// </value>
  277. public bool WasCommitted
  278. {
  279. get { return committed; }
  280. }
  281. public bool IsActive
  282. {
  283. get { return begun && !rolledBack && !committed; }
  284. }
  285. public IsolationLevel IsolationLevel
  286. {
  287. get { return trans.IsolationLevel; }
  288. }
  289. void CloseIfRequired()
  290. {
  291. //bool close = session.ShouldAutoClose() && !transactionContext.isClosed();
  292. //if (close)
  293. //{
  294. // transactionContext.managedClose();
  295. //}
  296. }
  297. #region System.IDisposable Members
  298. /// <summary>
  299. /// A flag to indicate if <c>Disose()</c> has been called.
  300. /// </summary>
  301. private bool _isAlreadyDisposed;
  302. private Guid sessionId;
  303. /// <summary>
  304. /// Finalizer that ensures the object is correctly disposed of.
  305. /// </summary>
  306. ~AdoTransaction()
  307. {
  308. Dispose(false);
  309. }
  310. /// <summary>
  311. /// Takes care of freeing the managed and unmanaged resources that
  312. /// this class is responsible for.
  313. /// </summary>
  314. public void Dispose()
  315. {
  316. Dispose(true);
  317. }
  318. /// <summary>
  319. /// Takes care of freeing the managed and unmanaged resources that
  320. /// this class is responsible for.
  321. /// </summary>
  322. /// <param name="isDisposing">Indicates if this AdoTransaction is being Disposed of or Finalized.</param>
  323. /// <remarks>
  324. /// If this AdoTransaction is being Finalized (<c>isDisposing==false</c>) then make sure not
  325. /// to call any methods that could potentially bring this AdoTransaction back to life.
  326. /// </remarks>
  327. protected virtual void Dispose(bool isDisposing)
  328. {
  329. using (SessionIdLoggingContext.CreateOrNull(sessionId))
  330. {
  331. if (_isAlreadyDisposed)
  332. {
  333. // don't dispose of multiple times.
  334. return;
  335. }
  336. _isAlreadyDisposed = true;
  337. // free managed resources that are being managed by the AdoTransaction if we
  338. // know this call came through Dispose()
  339. if (isDisposing)
  340. {
  341. if (trans != null)
  342. {
  343. trans.Dispose();
  344. trans = null;
  345. log.Debug("DbTransaction disposed.");
  346. }
  347. if (IsActive && session != null)
  348. {
  349. // Assume we are rolled back
  350. AfterTransactionCompletion(false);
  351. }
  352. // nothing for Finalizer to do - so tell the GC to ignore it
  353. GC.SuppressFinalize(this);
  354. }
  355. // free unmanaged resources here
  356. }
  357. }
  358. #endregion
  359. private void CheckNotDisposed()
  360. {
  361. if (_isAlreadyDisposed)
  362. {
  363. throw new ObjectDisposedException("AdoTransaction");
  364. }
  365. }
  366. private void CheckBegun()
  367. {
  368. if (!begun)
  369. {
  370. throw new TransactionException("Transaction not successfully started");
  371. }
  372. }
  373. private void CheckNotZombied()
  374. {
  375. if (trans != null && trans.Connection == null)
  376. {
  377. throw new TransactionException("Transaction not connected, or was disconnected");
  378. }
  379. }
  380. private void NotifyLocalSynchsBeforeTransactionCompletion()
  381. {
  382. #pragma warning disable 612
  383. if (synchronizations != null)
  384. {
  385. foreach (var sync in synchronizations)
  386. #pragma warning restore 612
  387. {
  388. try
  389. {
  390. sync.BeforeCompletion();
  391. }
  392. catch (Exception e)
  393. {
  394. log.Error(e, "exception calling user Synchronization");
  395. throw;
  396. }
  397. }
  398. }
  399. if (_completionSynchronizations == null)
  400. return;
  401. foreach (var sync in _completionSynchronizations)
  402. {
  403. sync.ExecuteBeforeTransactionCompletion();
  404. }
  405. }
  406. private void NotifyLocalSynchsAfterTransactionCompletion(bool success)
  407. {
  408. begun = false;
  409. #pragma warning disable 612
  410. if (synchronizations != null)
  411. {
  412. foreach (var sync in synchronizations)
  413. #pragma warning restore 612
  414. {
  415. try
  416. {
  417. sync.AfterCompletion(success);
  418. }
  419. catch (Exception e)
  420. {
  421. log.Error(e, "exception calling user Synchronization");
  422. }
  423. }
  424. }
  425. if (_completionSynchronizations == null)
  426. return;
  427. foreach (var sync in _completionSynchronizations)
  428. {
  429. try
  430. {
  431. sync.ExecuteAfterTransactionCompletion(success);
  432. }
  433. catch (Exception e)
  434. {
  435. log.Error(e, "exception calling user Synchronization");
  436. }
  437. }
  438. }
  439. }
  440. }