diff --git a/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs b/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs
index 1e1a9bc6d1..bc3709ea2a 100644
--- a/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs
+++ b/src/Umbraco.Core/Events/PassThroughEventDispatcher.cs
@@ -5,8 +5,10 @@ using System.Linq;
namespace Umbraco.Core.Events
{
///
- /// This event manager supports event cancellation and will raise the events as soon as they are tracked, it does not store tracked events
+ /// An IEventDispatcher that immediately raise all events.
///
+ /// This means that events will be raised during the scope transaction,
+ /// whatever happens, and the transaction could roll back in the end.
internal class PassThroughEventDispatcher : IEventDispatcher
{
public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null)
diff --git a/src/Umbraco.Core/Events/ScopeEventDispatcher.cs b/src/Umbraco.Core/Events/ScopeEventDispatcher.cs
index 1cf6660a40..eb4f8ec34e 100644
--- a/src/Umbraco.Core/Events/ScopeEventDispatcher.cs
+++ b/src/Umbraco.Core/Events/ScopeEventDispatcher.cs
@@ -3,11 +3,9 @@ using Umbraco.Core.IO;
namespace Umbraco.Core.Events
{
///
- /// This event manager is created for each scope and is aware of if it is nested in an outer scope
+ /// An IEventDispatcher that queues events, and raise them when the scope
+ /// exits and has been completed.
///
- ///
- /// The outer scope is the only scope that can raise events, the inner scope's will defer to the outer scope
- ///
internal class ScopeEventDispatcher : ScopeEventDispatcherBase
{
public ScopeEventDispatcher()
diff --git a/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs b/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs
index e7684f5be8..581f117979 100644
--- a/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs
+++ b/src/Umbraco.Core/Events/ScopeEventDispatcherBase.cs
@@ -6,6 +6,15 @@ using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Events
{
+ ///
+ /// An IEventDispatcher that queues events.
+ ///
+ ///
+ /// Can raise, or ignore, cancelable events, depending on option.
+ /// Implementations must override ScopeExitCompleted to define what
+ /// to do with the events when the scope exits and has been completed.
+ /// If the scope exits without being completed, events are ignored.
+ ///
public abstract class ScopeEventDispatcherBase : IEventDispatcher
{
//events will be enlisted in the order they are raised
diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IScopeUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IScopeUnitOfWork.cs
index e5232092c5..9c655d5d48 100644
--- a/src/Umbraco.Core/Persistence/UnitOfWork/IScopeUnitOfWork.cs
+++ b/src/Umbraco.Core/Persistence/UnitOfWork/IScopeUnitOfWork.cs
@@ -8,5 +8,6 @@ namespace Umbraco.Core.Persistence.UnitOfWork
IScope Scope { get; }
EventMessages Messages { get; }
IEventDispatcher Events { get; }
+ void Flush();
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWork.cs
index 204e7b0bfe..f8d168cdaa 100644
--- a/src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWork.cs
+++ b/src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWork.cs
@@ -160,7 +160,33 @@ namespace Umbraco.Core.Persistence.UnitOfWork
_key = Guid.NewGuid();
}
- public object Key
+ public void Flush()
+ {
+ if (_readOnly)
+ throw new NotSupportedException("This unit of work is read-only.");
+
+ while (_operations.Count > 0)
+ {
+ var operation = _operations.Dequeue();
+ switch (operation.Type)
+ {
+ case TransactionType.Insert:
+ operation.Repository.PersistNewItem(operation.Entity);
+ break;
+ case TransactionType.Delete:
+ operation.Repository.PersistDeletedItem(operation.Entity);
+ break;
+ case TransactionType.Update:
+ operation.Repository.PersistUpdatedItem(operation.Entity);
+ break;
+ }
+ }
+
+ _operations.Clear();
+ _key = Guid.NewGuid();
+ }
+
+ public object Key
{
get { return _key; }
}
diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs
index f08b9f2e6d..bfee772872 100644
--- a/src/Umbraco.Core/Scoping/Scope.cs
+++ b/src/Umbraco.Core/Scoping/Scope.cs
@@ -465,6 +465,7 @@ namespace Umbraco.Core.Scoping
}
finally
{
+ // removes the ambient context (ambient scope already gone)
_scopeProvider.SetAmbient(null);
}
}
diff --git a/src/Umbraco.Core/Scoping/ScopeContext.cs b/src/Umbraco.Core/Scoping/ScopeContext.cs
index cca0be560d..c79f02a0fa 100644
--- a/src/Umbraco.Core/Scoping/ScopeContext.cs
+++ b/src/Umbraco.Core/Scoping/ScopeContext.cs
@@ -1,16 +1,23 @@
using System;
using System.Collections.Generic;
+using System.Linq;
namespace Umbraco.Core.Scoping
{
public class ScopeContext : IInstanceIdentifiable
{
private Dictionary _enlisted;
+ private bool _exiting;
public void ScopeExit(bool completed)
{
+ if (_enlisted == null)
+ return;
+
+ _exiting = true;
+
List exceptions = null;
- foreach (var enlisted in Enlisted.Values)
+ foreach (var enlisted in _enlisted.Values.OrderBy(x => x.Priority))
{
try
{
@@ -23,6 +30,7 @@ namespace Umbraco.Core.Scoping
exceptions.Add(e);
}
}
+
if (exceptions != null)
throw new AggregateException("Exceptions were thrown by listed actions.", exceptions);
}
@@ -42,73 +50,71 @@ namespace Umbraco.Core.Scoping
private interface IEnlistedObject
{
void Execute(bool completed);
+ int Priority { get; }
}
private class EnlistedObject : IEnlistedObject
{
private readonly Action _action;
- public EnlistedObject(T item)
- {
- Item = item;
- }
-
- public EnlistedObject(T item, Action action)
+ public EnlistedObject(T item, Action action, int priority)
{
Item = item;
+ Priority = priority;
_action = action;
}
public T Item { get; private set; }
+ public int Priority { get; private set; }
+
public void Execute(bool completed)
{
_action(completed, Item);
}
}
- ///
+ // todo: replace with optional parameters when we can break things
public T Enlist(string key, Func creator)
{
- IEnlistedObject enlisted;
- if (Enlisted.TryGetValue(key, out enlisted))
- {
- var enlistedAs = enlisted as EnlistedObject;
- if (enlistedAs == null) throw new Exception("An item with a different type has already been enlisted with the same key.");
- return enlistedAs.Item;
- }
- var enlistedOfT = new EnlistedObject(creator());
- Enlisted[key] = enlistedOfT;
- return enlistedOfT.Item;
+ return Enlist(key, creator, null, 100);
}
- ///
+ // todo: replace with optional parameters when we can break things
public T Enlist(string key, Func creator, Action action)
{
- IEnlistedObject enlisted;
- if (Enlisted.TryGetValue(key, out enlisted))
- {
- var enlistedAs = enlisted as EnlistedObject;
- if (enlistedAs == null) throw new Exception("An item with a different type has already been enlisted with the same key.");
- return enlistedAs.Item;
- }
- var enlistedOfT = new EnlistedObject(creator(), action);
- Enlisted[key] = enlistedOfT;
- return enlistedOfT.Item;
+ return Enlist(key, creator, action, 100);
}
- ///
+ // todo: replace with optional parameters when we can break things
public void Enlist(string key, Action action)
{
+ Enlist