1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle
.ActiveRecord
18 using System
.Collections
.Generic
;
19 using System
.ComponentModel
;
22 using Castle
.ActiveRecord
.Framework
.Scopes
;
25 /// Defines the transaction scope behavior
27 public enum TransactionMode
30 /// Inherits a transaction previously create on
31 /// the current context.
35 /// Always create an isolated transaction context.
41 /// Governs the <see cref="TransactionScope"/> behavior
42 /// on dispose if neither <see cref="TransactionScope.VoteCommit"/>
43 /// nor <see cref="TransactionScope.VoteRollBack"/> was called
48 /// Should commit the transaction, unless <see cref="TransactionScope.VoteRollBack"/>
49 /// was called before the disposing the scope (this is the default behavior)
53 /// Should rollback the transaction, unless <see cref="TransactionScope.VoteCommit"/>
54 /// was called before the disposing the scope
60 /// Implementation of <see cref="ISessionScope"/> to
61 /// provide transaction semantics
63 public class TransactionScope
: SessionScope
65 private static readonly object CompletedEvent
= new object();
67 private readonly TransactionMode mode
;
68 private readonly IsolationLevel isolationLevel
;
69 private readonly OnDispose onDisposeBehavior
;
70 private readonly IDictionary
<ISession
, ITransaction
> transactions
= new Dictionary
<ISession
, ITransaction
>();
71 private readonly TransactionScope parentTransactionScope
;
72 private readonly AbstractScope parentSimpleScope
;
73 private readonly EventHandlerList events
= new EventHandlerList();
74 private bool rollbackOnly
, setForCommit
;
77 /// Initializes a new instance of the <see cref="TransactionScope"/> class.
79 public TransactionScope() : this(TransactionMode
.New
)
84 /// Initializes a new instance of the <see cref="TransactionScope"/> class.
86 /// <param name="onDisposeBehavior">The on dispose behavior.</param>
87 public TransactionScope(OnDispose onDisposeBehavior
) : this(TransactionMode
.New
, onDisposeBehavior
)
92 /// Initializes a new instance of the <see cref="TransactionScope"/> class.
94 /// <param name="mode">Whatever to create a new transaction or inherits an existing one</param>
95 public TransactionScope(TransactionMode mode
) : this(mode
, IsolationLevel
.Unspecified
, OnDispose
.Commit
)
100 /// Initializes a new instance of the <see cref="TransactionScope"/> class.
102 /// <param name="mode">Whatever to create a new transaction or inherits an existing one</param>
103 /// <param name="onDisposeBehavior">The on dispose behavior.</param>
104 public TransactionScope(TransactionMode mode
, OnDispose onDisposeBehavior
) : this(mode
, IsolationLevel
.Unspecified
, onDisposeBehavior
)
109 /// Initializes a new instance of the <see cref="TransactionScope"/> class.
111 /// <param name="mode">Whatever to create a new transaction or inherits an existing one</param>
112 /// <param name="isolationLevel">The transaction isolation level.</param>
113 /// <param name="onDisposeBehavior">The on dispose behavior.</param>
114 public TransactionScope(TransactionMode mode
, IsolationLevel isolationLevel
, OnDispose onDisposeBehavior
)
115 : base(FlushAction
.Auto
, SessionScopeType
.Transactional
)
118 this.isolationLevel
= isolationLevel
;
119 this.onDisposeBehavior
= onDisposeBehavior
;
121 bool preferenceForTransactionScope
= mode
== TransactionMode
.Inherits
? true : false;
123 ISessionScope previousScope
= ScopeUtil
.FindPreviousScope(this, preferenceForTransactionScope
);
125 if (previousScope
!= null)
127 if (previousScope
.ScopeType
== SessionScopeType
.Transactional
)
129 parentTransactionScope
= previousScope
as TransactionScope
;
133 // This is not a safe cast. Reconsider it
134 parentSimpleScope
= (AbstractScope
) previousScope
;
136 foreach(ISession session
in parentSimpleScope
.GetSessions())
138 EnsureHasTransaction(session
);
144 #region OnTransactionCompleted event
147 /// This event is raised when a transaction is completed
149 public event EventHandler OnTransactionCompleted
153 if (parentTransactionScope
!= null)
155 parentTransactionScope
.OnTransactionCompleted
+= value;
159 events
.AddHandler(CompletedEvent
, value);
164 if (parentTransactionScope
!= null)
166 parentTransactionScope
.OnTransactionCompleted
-= value;
170 events
.RemoveHandler(CompletedEvent
, value);
178 /// Votes to roll back the transaction
180 public void VoteRollBack()
182 if (mode
== TransactionMode
.Inherits
&& parentTransactionScope
!= null)
184 parentTransactionScope
.VoteRollBack();
190 /// Votes to commit the transaction
192 public void VoteCommit()
196 throw new TransactionException("The transaction was marked as rollback " + "only - by itself or one of the nested transactions");
202 /// This method is invoked when the
203 /// <see cref="Castle.ActiveRecord.Framework.ISessionFactoryHolder"/>
204 /// instance needs a session instance. Instead of creating one it interrogates
205 /// the active scope for one. The scope implementation must check if it
206 /// has a session registered for the given key.
207 /// <seealso cref="RegisterSession"/>
209 /// <param name="key">an object instance</param>
211 /// <c>true</c> if the key exists within this scope instance
213 public override bool IsKeyKnown(object key
)
215 if (mode
== TransactionMode
.Inherits
&& parentTransactionScope
!= null)
217 return parentTransactionScope
.IsKeyKnown(key
);
220 bool keyKnown
= false;
222 if (parentSimpleScope
!= null)
224 keyKnown
= parentSimpleScope
.IsKeyKnown(key
);
227 return keyKnown
? true : base.IsKeyKnown(key
);
231 /// This method is invoked when no session was available
232 /// at and the <see cref="Castle.ActiveRecord.Framework.ISessionFactoryHolder"/>
233 /// just created one. So it registers the session created
234 /// within this scope using a key. The scope implementation
235 /// shouldn't make any assumption on what the key
236 /// actually is as we reserve the right to change it
237 /// <seealso cref="IsKeyKnown"/>
239 /// <param name="key">an object instance</param>
240 /// <param name="session">An instance of <c>ISession</c></param>
241 public override void RegisterSession(object key
, ISession session
)
243 if (mode
== TransactionMode
.Inherits
&& parentTransactionScope
!= null)
245 parentTransactionScope
.RegisterSession(key
, session
);
247 else if (parentSimpleScope
!= null)
249 parentSimpleScope
.RegisterSession(key
, session
);
252 base.RegisterSession(key
, session
);
256 /// This method should return the session instance associated with the key.
258 /// <param name="key">an object instance</param>
260 /// the session instance or null if none was found
262 public override ISession
GetSession(object key
)
264 if (mode
== TransactionMode
.Inherits
&& parentTransactionScope
!= null)
266 return parentTransactionScope
.GetSession(key
);
269 ISession session
= null;
271 if (parentSimpleScope
!= null)
273 session
= parentSimpleScope
.GetSession(key
);
276 session
= session
?? base.GetSession(key
);
278 EnsureHasTransaction(session
);
284 /// Flushes the sessions that this scope
285 /// or its parents are maintaining
287 public override void Flush()
289 if (mode
== TransactionMode
.Inherits
&& parentTransactionScope
!= null)
291 parentTransactionScope
.Flush();
294 if (parentSimpleScope
!= null)
296 parentSimpleScope
.Flush();
303 /// Ensures that a transaction exist, creating one if neccecary
305 /// <param name="session">The session.</param>
306 protected internal void EnsureHasTransaction(ISession session
)
308 if (!transactions
.ContainsKey(session
))
310 session
.FlushMode
= FlushMode
.Commit
;
312 ITransaction transaction
;
314 if (isolationLevel
== IsolationLevel
.Unspecified
)
316 transaction
= session
.BeginTransaction();
320 transaction
= session
.BeginTransaction(isolationLevel
);
323 transactions
.Add(session
, transaction
);
328 /// Initializes the current transaction scope using the session
330 /// <param name="session">The session.</param>
331 protected override void Initialize(ISession session
)
333 if (mode
== TransactionMode
.Inherits
&& parentTransactionScope
!= null)
335 parentTransactionScope
.EnsureHasTransaction(session
);
339 EnsureHasTransaction(session
);
343 /// Dispose of this scope
345 /// <param name="sessions">The sessions.</param>
346 protected override void PerformDisposal(ICollection
<ISession
> sessions
)
348 if (!setForCommit
&& !rollbackOnly
) // Neither VoteCommit or VoteRollback were called
350 if (onDisposeBehavior
== OnDispose
.Rollback
)
356 if (mode
== TransactionMode
.Inherits
&& parentTransactionScope
!= null)
358 // In this case it's not up to this instance to perform the clean up
362 Exception transactionError
= null;
364 foreach(ITransaction transaction
in transactions
.Values
)
370 transaction
.Rollback();
374 transaction
.Commit();
379 transactionError
= ex
;
381 transaction
.Dispose();
385 if (parentSimpleScope
== null)
387 // No flush necessary, but we should close the session
389 PerformDisposal(sessions
, false, true);
395 // Cancel all pending changes
396 // (not sure whether this is a good idea, it should be scoped
398 foreach(ISession session
in parentSimpleScope
.GetSessions())
407 if (transactionError
!= null)
409 throw new TransactionException("An error occured when trying to dispose the transaction", transactionError
);
414 /// This is called when a session has a failure
416 /// <param name="session">the session</param>
417 public override void FailSession(ISession session
)
423 /// Discards the sessions.
425 /// <param name="sessions">The sessions.</param>
426 protected internal override void DiscardSessions(ICollection
<ISession
> sessions
)
428 if (parentSimpleScope
!= null)
430 parentSimpleScope
.DiscardSessions(sessions
);
435 /// Raises the on completed event
437 private void RaiseOnCompleted()
439 EventHandler handler
= (EventHandler
) events
[CompletedEvent
];
443 handler(this, EventArgs
.Empty
);