diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt
index 1672ad53d0..8e2ddf83f7 100644
--- a/build/UmbracoVersion.txt
+++ b/build/UmbracoVersion.txt
@@ -1,3 +1,3 @@
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
7.6.0
-alpha048
+alpha046
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index b092457e25..dabe368215 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -12,4 +12,4 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.6.0")]
-[assembly: AssemblyInformationalVersion("7.6.0-alpha048")]
\ No newline at end of file
+[assembly: AssemblyInformationalVersion("7.6.0-alpha046")]
\ No newline at end of file
diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs
index e5bac73edb..88c2d65926 100644
--- a/src/Umbraco.Core/ApplicationContext.cs
+++ b/src/Umbraco.Core/ApplicationContext.cs
@@ -7,6 +7,7 @@ using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.ObjectResolution;
using Umbraco.Core.Profiling;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
@@ -162,6 +163,9 @@ namespace Umbraco.Core
///
public static ApplicationContext Current { get; internal set; }
+ // fixme
+ public IScopeProvider ScopeProvider { get { return DatabaseContext.ScopeProvider; } }
+
///
/// Returns the application wide cache accessor
///
diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
index cd759cba82..7fdbaee91c 100644
--- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration
/// Gets the version comment (like beta or RC).
///
/// The version comment.
- public static string CurrentComment { get { return "alpha048"; } }
+ public static string CurrentComment { get { return "alpha046"; } }
// Get the version of the umbraco.dll by looking at a class in that dll
// Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx
diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs
index e98cd31dbf..bc8af45504 100644
--- a/src/Umbraco.Core/CoreBootManager.cs
+++ b/src/Umbraco.Core/CoreBootManager.cs
@@ -28,6 +28,7 @@ using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Core.Publishing;
using Umbraco.Core.Macros;
using Umbraco.Core.Manifest;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
using Umbraco.Core.Strings;
@@ -108,8 +109,11 @@ namespace Umbraco.Core
var dbFactory = new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, ProfilingLogger.Logger);
Database.Mapper = new PetaPocoMapper();
+ var scopeProvider = new ScopeProvider(dbFactory);
+ dbFactory.ScopeProvider = scopeProvider;
+
var dbContext = new DatabaseContext(
- dbFactory,
+ scopeProvider,
ProfilingLogger.Logger,
SqlSyntaxProviders.CreateDefault(ProfilingLogger.Logger));
@@ -117,7 +121,7 @@ namespace Umbraco.Core
dbContext.Initialize();
//get the service context
- var serviceContext = CreateServiceContext(dbContext, dbFactory);
+ var serviceContext = CreateServiceContext(dbContext, scopeProvider);
//set property and singleton from response
ApplicationContext.Current = ApplicationContext = CreateApplicationContext(dbContext, serviceContext);
@@ -160,15 +164,15 @@ namespace Umbraco.Core
/// Creates and returns the service context for the app
///
///
- ///
+ ///
///
- protected virtual ServiceContext CreateServiceContext(DatabaseContext dbContext, IDatabaseFactory dbFactory)
+ protected virtual ServiceContext CreateServiceContext(DatabaseContext dbContext, IScopeProvider scopeProvider)
{
//default transient factory
var msgFactory = new TransientMessagesFactory();
return new ServiceContext(
new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()),
- new PetaPocoUnitOfWorkProvider(dbFactory),
+ new PetaPocoUnitOfWorkProvider(scopeProvider),
new FileUnitOfWorkProvider(),
new PublishingStrategy(msgFactory, ProfilingLogger.Logger),
ApplicationCache,
diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs
index 483e124e35..2f7641795f 100644
--- a/src/Umbraco.Core/DatabaseContext.cs
+++ b/src/Umbraco.Core/DatabaseContext.cs
@@ -14,6 +14,7 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Migrations;
using Umbraco.Core.Persistence.Migrations.Initial;
using Umbraco.Core.Persistence.SqlSyntax;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
namespace Umbraco.Core
@@ -26,7 +27,7 @@ namespace Umbraco.Core
///
public class DatabaseContext
{
- private readonly IDatabaseFactory _factory;
+ internal readonly ScopeProvider ScopeProvider;
private readonly ILogger _logger;
private readonly SqlSyntaxProviders _syntaxProviders;
private bool _configured;
@@ -41,8 +42,8 @@ namespace Umbraco.Core
private const int ConnectionCheckMinutes = 1;
[Obsolete("Use the constructor specifying all dependencies instead")]
- public DatabaseContext(IDatabaseFactory factory)
- : this(factory, LoggerResolver.Current.Logger, new SqlSyntaxProviders(new ISqlSyntaxProvider[]
+ public DatabaseContext(IScopeProvider scopeProvider)
+ : this(scopeProvider, LoggerResolver.Current.Logger, new SqlSyntaxProviders(new ISqlSyntaxProvider[]
{
new MySqlSyntaxProvider(LoggerResolver.Current.Logger),
new SqlCeSyntaxProvider(),
@@ -54,16 +55,16 @@ namespace Umbraco.Core
///
/// Default constructor
///
- ///
+ ///
///
///
- public DatabaseContext(IDatabaseFactory factory, ILogger logger, SqlSyntaxProviders syntaxProviders)
+ public DatabaseContext(IScopeProvider scopeProvider, ILogger logger, SqlSyntaxProviders syntaxProviders)
{
- if (factory == null) throw new ArgumentNullException("factory");
+ if (scopeProvider == null) throw new ArgumentNullException("scopeProvider");
if (logger == null) throw new ArgumentNullException("logger");
if (syntaxProviders == null) throw new ArgumentNullException("syntaxProviders");
- _factory = factory;
+ ScopeProvider = (ScopeProvider) scopeProvider; // fixme ugly
_logger = logger;
_syntaxProviders = syntaxProviders;
}
@@ -71,16 +72,16 @@ namespace Umbraco.Core
///
/// Create a configured DatabaseContext
///
- ///
+ ///
///
///
///
- public DatabaseContext(IDatabaseFactory factory, ILogger logger, ISqlSyntaxProvider sqlSyntax, string providerName)
+ public DatabaseContext(IScopeProvider scopeProvider, ILogger logger, ISqlSyntaxProvider sqlSyntax, string providerName)
{
_providerName = providerName;
SqlSyntax = sqlSyntax;
SqlSyntaxContext.SqlSyntaxProvider = SqlSyntax;
- _factory = factory;
+ ScopeProvider = (ScopeProvider) scopeProvider; // fixme ugly
_logger = logger;
_configured = true;
}
@@ -110,7 +111,11 @@ namespace Umbraco.Core
///
public virtual UmbracoDatabase Database
{
- get { return _factory.CreateDatabase(); }
+ get
+ {
+ var scope = ScopeProvider.AmbientScope;
+ return scope != null ? scope.Database : ScopeProvider.CreateNoScope().Database;
+ }
}
///
@@ -123,6 +128,8 @@ namespace Umbraco.Core
/// will be properly removed from call context and does not interfere with anything else. In most case
/// it is not replacing anything, just temporarily installing a database in context.
///
+ // fixme - this should just entirely be replaced by Scope?
+ /*
public virtual IDisposable UseSafeDatabase(bool force = false)
{
var factory = _factory as DefaultDatabaseFactory;
@@ -138,6 +145,7 @@ namespace Umbraco.Core
// create a new, temp, database (will be disposed with UsingDatabase)
return new UsingDatabase(null, factory.CreateDatabase());
}
+ */
///
/// Boolean indicating whether the database has been configured
@@ -159,7 +167,7 @@ namespace Umbraco.Core
//Don't check again if the timeout period hasn't elapsed
//this ensures we don't keep checking the connection too many times in a row like during startup.
- //Do check if the _connectionLastChecked is null which means we're just initializing or it could
+ //Do check if the _connectionLastChecked is null which means we're just initializing or it could
//not connect last time it was checked.
if ((_connectionLastChecked.HasValue && (DateTime.Now - _connectionLastChecked.Value).TotalMinutes > ConnectionCheckMinutes)
|| _connectionLastChecked.HasValue == false)
@@ -814,6 +822,7 @@ namespace Umbraco.Core
return true;
}
+ /*
private class UsingDatabase : IDisposable
{
private readonly UmbracoDatabase _orig;
@@ -836,5 +845,6 @@ namespace Umbraco.Core
GC.SuppressFinalize(this);
}
}
+ */
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs
index fc981b0280..69026962ac 100644
--- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs
+++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs
@@ -1,8 +1,6 @@
using System;
-using System.Collections.Generic;
-using System.Runtime.Remoting.Messaging;
-using System.Web;
using Umbraco.Core.Logging;
+using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence
{
@@ -14,30 +12,17 @@ namespace Umbraco.Core.Persistence
/// it will create one per context, otherwise it will be a global singleton object which is NOT thread safe
/// since we need (at least) a new instance of the database object per thread.
///
- internal class DefaultDatabaseFactory : DisposableObject, IDatabaseFactory
+ internal class DefaultDatabaseFactory : DisposableObject, IDatabaseFactory2
{
private readonly string _connectionStringName;
private readonly ILogger _logger;
public string ConnectionString { get; private set; }
public string ProviderName { get; private set; }
- // NO! see notes in v8 HybridAccessorBase
- //[ThreadStatic]
- //private static volatile UmbracoDatabase _nonHttpInstance;
+ //private static readonly object Locker = new object();
- private const string ItemKey = "Umbraco.Core.Persistence.DefaultDatabaseFactory";
-
- private static UmbracoDatabase NonContextValue
- {
- get { return (UmbracoDatabase) CallContext.LogicalGetData(ItemKey); }
- set
- {
- if (value == null) CallContext.FreeNamedDataSlot(ItemKey);
- else CallContext.LogicalSetData(ItemKey, value);
- }
- }
-
- private static readonly object Locker = new object();
+ // bwc imposes a weird x-dependency between database factory and scope provider...
+ public ScopeProvider ScopeProvider { get; set; }
///
/// Constructor accepting custom connection string
@@ -76,6 +61,10 @@ namespace Umbraco.Core.Persistence
public UmbracoDatabase CreateDatabase()
{
+ var scope = ScopeProvider.AmbientScope;
+ return scope != null ? scope.Database : ScopeProvider.CreateNoScope().Database;
+
+ /*
UmbracoDatabase database;
// gets or creates a database, using either the call context (if no http context) or
@@ -128,9 +117,11 @@ namespace Umbraco.Core.Persistence
}
return database;
+ */
}
// called by UmbracoDatabase when disposed, so that the factory can de-list it from context
+ /*
internal void OnDispose(UmbracoDatabase disposing)
{
var value = disposing;
@@ -168,6 +159,7 @@ namespace Umbraco.Core.Persistence
_databases.Remove(value);
#endif
}
+ */
#if DEBUG_DATABASES
// helps identifying when non-httpContext databases are created by logging the stack trace
@@ -202,6 +194,12 @@ namespace Umbraco.Core.Persistence
CallContext
}
+ public UmbracoDatabase CreateNewDatabase()
+ {
+ return CreateDatabaseInstance(ContextOwner.None);
+
+ }
+
internal UmbracoDatabase CreateDatabaseInstance(ContextOwner contextOwner)
{
var database = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
@@ -221,6 +219,7 @@ namespace Umbraco.Core.Persistence
protected override void DisposeResources()
{
+ /*
UmbracoDatabase database;
if (HttpContext.Current == null)
@@ -239,88 +238,7 @@ namespace Umbraco.Core.Persistence
}
if (database != null) database.Dispose(); // removes it from call context
+ */
}
-
- // during tests, the thread static var can leak between tests
- // this method provides a way to force-reset the variable
- internal void ResetForTests()
- {
- var value = NonContextValue;
- if (value != null) value.Dispose();
- NonContextValue = null;
- }
-
- #region SafeCallContext
-
- // see notes in SafeCallContext - need to do this since we are using
- // the logical call context...
-
- static DefaultDatabaseFactory()
- {
- SafeCallContext.Register(DetachAmbientDatabase, AttachAmbientDatabase);
- }
-
- // gets a value indicating whether there is an ambient database
- internal static bool HasAmbientDatabase
- {
- get
- {
- return HttpContext.Current == null
- ? NonContextValue != null
- : HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] != null;
- }
- }
-
- // detaches the current database
- // ie returns the database and remove it from whatever is "context"
- internal static UmbracoDatabase DetachAmbientDatabase()
- {
- UmbracoDatabase database;
-
- if (HttpContext.Current == null)
- {
- database = NonContextValue;
- NonContextValue = null;
- }
- else
- {
- database = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)];
- HttpContext.Current.Items.Remove(typeof (DefaultDatabaseFactory));
- }
-
- if (database != null) database.ContextOwner = ContextOwner.None;
- return database;
- }
-
- // attach a current database
- // ie assign it to whatever is "context"
- // throws if there already is a database
- internal static void AttachAmbientDatabase(object o)
- {
- var database = o as UmbracoDatabase;
- if (o != null && database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o");
-
- var ambient = DetachAmbientDatabase();
- if (ambient != null) ambient.Dispose();
-
- if (HttpContext.Current == null)
- {
- //if (NonContextValue != null) throw new InvalidOperationException();
- if (database == null) return;
-
- NonContextValue = database;
- database.ContextOwner = ContextOwner.CallContext;
- }
- else
- {
- //if (HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] != null) throw new InvalidOperationException();
- if (database == null) return;
-
- HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] = database;
- database.ContextOwner = ContextOwner.HttpContext;
- }
- }
-
- #endregion
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/IDatabaseFactory.cs b/src/Umbraco.Core/Persistence/IDatabaseFactory.cs
index b0efb7f94a..3a80739ec7 100644
--- a/src/Umbraco.Core/Persistence/IDatabaseFactory.cs
+++ b/src/Umbraco.Core/Persistence/IDatabaseFactory.cs
@@ -7,6 +7,13 @@ namespace Umbraco.Core.Persistence
///
public interface IDatabaseFactory : IDisposable
{
+ // gets or creates the ambient database
UmbracoDatabase CreateDatabase();
}
+
+ public interface IDatabaseFactory2 : IDatabaseFactory
+ {
+ // creates a new database
+ UmbracoDatabase CreateNewDatabase();
+ }
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs
index 0b370c9978..44a3db9424 100644
--- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs
+++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence
/// can then override any additional execution (such as additional loggging, functionality, etc...) that we need to without breaking compatibility since we'll always be exposing
/// this object instead of the base PetaPoco database object.
///
- public class UmbracoDatabase : Database, IDisposeOnRequestEnd
+ public class UmbracoDatabase : Database
{
private readonly ILogger _logger;
private readonly Guid _instanceId = Guid.NewGuid();
@@ -259,6 +259,7 @@ namespace Umbraco.Core.Persistence
base.BuildSqlDbSpecificPagingQuery(databaseType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage);
}
+ /*
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
@@ -267,5 +268,6 @@ namespace Umbraco.Core.Persistence
#endif
if (DatabaseFactory != null) DatabaseFactory.OnDispose(this);
}
+ */
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs
index a5337e854c..6c71614c1a 100644
--- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs
+++ b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using Umbraco.Core.Models.EntityBase;
+using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.UnitOfWork
{
@@ -10,6 +10,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork
///
internal class PetaPocoUnitOfWork : DisposableObject, IDatabaseUnitOfWork
{
+ private readonly IScope _scope;
///
/// Used for testing
@@ -19,16 +20,16 @@ namespace Umbraco.Core.Persistence.UnitOfWork
private Guid _key;
private readonly Queue _operations = new Queue();
- ///
- /// Creates a new unit of work instance
- ///
- ///
- ///
- /// This should normally not be used directly and should be created with the UnitOfWorkProvider
- ///
- internal PetaPocoUnitOfWork(UmbracoDatabase database)
- {
- Database = database;
+ ///
+ /// Creates a new unit of work instance
+ ///
+ ///
+ ///
+ /// This should normally not be used directly and should be created with the UnitOfWorkProvider
+ ///
+ internal PetaPocoUnitOfWork(IScopeProvider scopeProvider)
+ {
+ _scope = scopeProvider.CreateScope();
_key = Guid.NewGuid();
InstanceId = Guid.NewGuid();
}
@@ -139,7 +140,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork
get { return _key; }
}
- public UmbracoDatabase Database { get; private set; }
+ public UmbracoDatabase Database {get { return _scope.Database; } }
#region Operation
@@ -178,6 +179,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork
protected override void DisposeResources()
{
_operations.Clear();
+ _scope.Dispose();
}
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs
index 97d066d572..6b901d34f7 100644
--- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs
+++ b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs
@@ -1,6 +1,7 @@
using System;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
+using Umbraco.Core.Scoping;
namespace Umbraco.Core.Persistence.UnitOfWork
{
@@ -9,25 +10,25 @@ namespace Umbraco.Core.Persistence.UnitOfWork
///
public class PetaPocoUnitOfWorkProvider : IDatabaseUnitOfWorkProvider
{
- private readonly IDatabaseFactory _dbFactory;
+ private readonly IScopeProvider _scopeProvider;
[Obsolete("Use the constructor specifying an ILogger instead")]
public PetaPocoUnitOfWorkProvider()
- : this(new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, LoggerResolver.Current.Logger))
+ : this(new ScopeProvider(new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, LoggerResolver.Current.Logger)))
{
}
[Obsolete("Use the constructor specifying an ILogger instead")]
public PetaPocoUnitOfWorkProvider(string connectionString, string providerName)
- : this(new DefaultDatabaseFactory(connectionString, providerName, LoggerResolver.Current.Logger))
+ : this(new ScopeProvider(new DefaultDatabaseFactory(connectionString, providerName, LoggerResolver.Current.Logger)))
{ }
///
/// Parameterless constructor uses defaults
///
public PetaPocoUnitOfWorkProvider(ILogger logger)
- : this(new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, logger))
+ : this(new ScopeProvider(new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, logger)))
{
}
@@ -39,17 +40,17 @@ namespace Umbraco.Core.Persistence.UnitOfWork
/// Connection String to use with Database
/// Database Provider for the Connection String
public PetaPocoUnitOfWorkProvider(ILogger logger, string connectionString, string providerName)
- : this(new DefaultDatabaseFactory(connectionString, providerName, logger))
+ : this(new ScopeProvider(new DefaultDatabaseFactory(connectionString, providerName, logger)))
{ }
///
/// Constructor accepting an IDatabaseFactory instance
///
- ///
- public PetaPocoUnitOfWorkProvider(IDatabaseFactory dbFactory)
+ ///
+ public PetaPocoUnitOfWorkProvider(IScopeProvider scopeProvider)
{
- Mandate.ParameterNotNull(dbFactory, "dbFactory");
- _dbFactory = dbFactory;
+ Mandate.ParameterNotNull(scopeProvider, "scopeProvider");
+ _scopeProvider = scopeProvider;
}
#region Implementation of IUnitOfWorkProvider
@@ -65,7 +66,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork
///
public IDatabaseUnitOfWork GetUnitOfWork()
{
- return new PetaPocoUnitOfWork(_dbFactory.CreateDatabase());
+ return new PetaPocoUnitOfWork(_scopeProvider);
}
#endregion
@@ -76,6 +77,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork
///
internal static IDatabaseUnitOfWork CreateUnitOfWork(ILogger logger)
{
+ // fixme wtf?
var provider = new PetaPocoUnitOfWorkProvider(logger);
return provider.GetUnitOfWork();
}
diff --git a/src/Umbraco.Core/Scoping/IScope.cs b/src/Umbraco.Core/Scoping/IScope.cs
new file mode 100644
index 0000000000..f56fcdd018
--- /dev/null
+++ b/src/Umbraco.Core/Scoping/IScope.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+using Umbraco.Core.Events;
+using Umbraco.Core.Persistence;
+
+namespace Umbraco.Core.Scoping
+{
+ public interface IScope : IDisposeOnRequestEnd // implies IDisposable
+ {
+ UmbracoDatabase Database { get; }
+ IList Messages { get; }
+
+ void Complete();
+ }
+}
diff --git a/src/Umbraco.Core/Scoping/IScopeProvider.cs b/src/Umbraco.Core/Scoping/IScopeProvider.cs
new file mode 100644
index 0000000000..112a467cc3
--- /dev/null
+++ b/src/Umbraco.Core/Scoping/IScopeProvider.cs
@@ -0,0 +1,10 @@
+namespace Umbraco.Core.Scoping
+{
+ public interface IScopeProvider
+ {
+ IScope CreateScope();
+ IScope CreateDetachedScope();
+ void AttachScope(IScope scope);
+ IScope DetachScope();
+ }
+}
diff --git a/src/Umbraco.Core/Scoping/IScopeProviderInternal.cs b/src/Umbraco.Core/Scoping/IScopeProviderInternal.cs
new file mode 100644
index 0000000000..ad0419f7a5
--- /dev/null
+++ b/src/Umbraco.Core/Scoping/IScopeProviderInternal.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Umbraco.Core.Scoping
+{
+ internal interface IScopeProviderInternal : IScopeProvider
+ {
+ }
+}
diff --git a/src/Umbraco.Core/Scoping/NoScope.cs b/src/Umbraco.Core/Scoping/NoScope.cs
new file mode 100644
index 0000000000..d72d201060
--- /dev/null
+++ b/src/Umbraco.Core/Scoping/NoScope.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Core.Events;
+using Umbraco.Core.Persistence;
+
+namespace Umbraco.Core.Scoping
+{
+ internal class NoScope : IScope
+ {
+ private readonly ScopeProvider _scopeProvider;
+
+ private UmbracoDatabase _database;
+ private IList _messages;
+
+ public NoScope(ScopeProvider scopeProvider)
+ {
+ _scopeProvider = scopeProvider;
+ }
+
+ public bool HasDatabase { get { return _database != null; } }
+
+ public UmbracoDatabase Database
+ {
+ get { return _database ?? (_database = _scopeProvider.DatabaseFactory.CreateNewDatabase()); }
+ }
+
+ public bool HasMessages { get { return _messages != null; } }
+
+ public IList Messages
+ {
+ get { return _messages ?? (_messages = new List()); }
+ }
+
+ public void Complete()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Dispose()
+ {
+ _scopeProvider.Disposing(this);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs
new file mode 100644
index 0000000000..327adec8ca
--- /dev/null
+++ b/src/Umbraco.Core/Scoping/Scope.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Core.Events;
+using Umbraco.Core.Persistence;
+
+namespace Umbraco.Core.Scoping
+{
+ internal class Scope : IScope
+ {
+ private readonly ScopeProvider _scopeProvider;
+ private bool? _completed;
+
+ private UmbracoDatabase _database;
+ private IList _messages;
+
+ public Scope(ScopeProvider scopeProvider, bool detachable = false)
+ {
+ _scopeProvider = scopeProvider;
+ Detachable = detachable;
+ }
+
+ public Scope(ScopeProvider scopeProvider, Scope parent)
+ : this(scopeProvider)
+ {
+ ParentScope = parent;
+ }
+
+ public Scope(ScopeProvider scopeProvider, NoScope noScope)
+ : this(scopeProvider)
+ {
+ // stealing everything from NoScope
+ _database = noScope.HasDatabase ? noScope.Database : null;
+ _messages = noScope.HasMessages ? noScope.Messages : null;
+ if (_database != null)
+ {
+ // must not be in a transaction
+ if (_database.Connection != null)
+ throw new Exception();
+ }
+ }
+
+ public bool Detachable { get; private set; }
+
+ public Scope ParentScope { get; set; }
+
+ public Scope OrigScope { get; set; }
+
+ public bool HasDatabase
+ {
+ get { return ParentScope == null ? _database != null : ParentScope.HasDatabase; }
+ }
+
+ public UmbracoDatabase Database
+ {
+ get
+ {
+ if (ParentScope != null) return ParentScope.Database;
+ if (_database != null)
+ {
+ if (_database.Connection == null) // stolen from noScope
+ _database.BeginTransaction(); // a scope implies a transaction, always
+ return _database;
+ }
+ _database = _scopeProvider.DatabaseFactory.CreateNewDatabase();
+ _database.BeginTransaction(); // a scope implies a transaction, always
+ return _database;
+ }
+ }
+
+ public bool HasMessages
+ {
+ get { return ParentScope == null ? _messages != null : ParentScope.HasMessages; }
+ }
+
+ public IList Messages
+ {
+ get
+ {
+ if (ParentScope != null) return ParentScope.Messages;
+ if (_messages == null)
+ _messages = new List();
+ return _messages;
+ }
+ }
+
+ public void Complete()
+ {
+ if (_completed.HasValue == false)
+ _completed = true;
+ }
+
+ public void CompleteChild(bool? completed)
+ {
+ if (completed.HasValue)
+ {
+ if (completed.Value)
+ {
+ // child did complete
+ // nothing to do
+ }
+ else
+ {
+ // child did not complete, we cannot complete
+ _completed = false;
+ }
+ }
+ else
+ {
+ // child did not complete, we cannot complete
+ _completed = false;
+ }
+ }
+
+ public void Dispose()
+ {
+ _scopeProvider.Disposing(this, _completed);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Scoping/ScopeProvider.cs b/src/Umbraco.Core/Scoping/ScopeProvider.cs
new file mode 100644
index 0000000000..cdb51e2017
--- /dev/null
+++ b/src/Umbraco.Core/Scoping/ScopeProvider.cs
@@ -0,0 +1,224 @@
+using System;
+using System.Runtime.Remoting.Messaging;
+using System.Web;
+using Umbraco.Core.Persistence;
+
+namespace Umbraco.Core.Scoping
+{
+ internal class ScopeProvider : IScopeProvider
+ {
+ public ScopeProvider(IDatabaseFactory2 databaseFactory)
+ {
+ DatabaseFactory = databaseFactory;
+ }
+
+ static ScopeProvider()
+ {
+ SafeCallContext.Register(
+ () =>
+ {
+ var scope = StaticAmbientScope;
+ StaticAmbientScope = null;
+ return scope;
+ },
+ scope =>
+ {
+ var ambient = StaticAmbientScope;
+ if (ambient != null)
+ ambient.Dispose();
+ StaticAmbientScope = (IScope) scope;
+ });
+ }
+
+ public IDatabaseFactory2 DatabaseFactory { get; private set; }
+
+ private const string ItemKey = "Umbraco.Core.Scoping.IScope";
+
+ private static IScope CallContextValue
+ {
+ get { return (IScope) CallContext.LogicalGetData(ItemKey); }
+ set
+ {
+ if (value == null) CallContext.FreeNamedDataSlot(ItemKey);
+ else CallContext.LogicalSetData(ItemKey, value);
+ }
+ }
+
+ private static IScope HttpContextValue
+ {
+ get { return (IScope) HttpContext.Current.Items[ItemKey]; }
+ set
+ {
+ if (value == null) HttpContext.Current.Items.Remove(ItemKey);
+ else HttpContext.Current.Items[ItemKey] = value;
+ }
+ }
+
+ private static IScope StaticAmbientScope
+ {
+ get { return HttpContext.Current == null ? CallContextValue : HttpContextValue; }
+ set
+ {
+ if (HttpContext.Current == null)
+ CallContextValue = value;
+ else
+ HttpContextValue = value;
+ }
+ }
+
+ public IScope AmbientScope
+ {
+ get { return StaticAmbientScope; }
+ set { StaticAmbientScope = value; }
+ }
+
+ // fixme should we do...
+ // using (var s = scopeProvider.AttachScope(other))
+ // {
+ // }
+ // can't because disposing => detach or commit? cannot tell!
+ // var scope = scopeProvider.CreateScope();
+ // scope = scopeProvider.Detach();
+ // scope.Detach();
+ // scopeProvider.Attach(scope);
+ // ... do things ...
+ // scopeProvider.Detach();
+ // scopeProvider.Attach(scope);
+ // scope.Dispose();
+
+ public IScope CreateDetachedScope()
+ {
+ return new Scope(this, true);
+ }
+
+ public void AttachScope(IScope other)
+ {
+ var otherScope = other as Scope;
+ if (otherScope == null)
+ throw new ArgumentException();
+
+ if (otherScope.Detachable == false)
+ throw new ArgumentException();
+
+ var ambient = AmbientScope;
+ if (ambient == null)
+ {
+ AmbientScope = other;
+ return;
+ }
+
+ var noScope = ambient as NoScope;
+ if (noScope != null)
+ throw new InvalidOperationException();
+
+ var scope = ambient as Scope;
+ if (ambient != null && scope == null)
+ throw new Exception();
+
+ otherScope.OrigScope = scope;
+ AmbientScope = otherScope;
+ }
+
+ public IScope DetachScope()
+ {
+ var ambient = AmbientScope;
+ if (ambient == null)
+ throw new InvalidOperationException();
+
+ var noScope = ambient as NoScope;
+ if (noScope != null)
+ throw new InvalidOperationException();
+
+ var scope = ambient as Scope;
+ if (scope == null)
+ throw new Exception();
+
+ if (scope.Detachable == false)
+ throw new InvalidOperationException();
+
+ AmbientScope = scope.OrigScope;
+ scope.OrigScope = null;
+ return scope;
+ }
+
+ public IScope CreateScope()
+ {
+ var ambient = AmbientScope;
+ if (ambient == null)
+ return AmbientScope = new Scope(this);
+
+ var noScope = ambient as NoScope;
+ if (noScope != null)
+ {
+ // peta poco nulls the shared connection after each command unless there's a trx
+ if (noScope.HasDatabase && noScope.Database.Connection != null)
+ throw new Exception();
+ return AmbientScope = new Scope(this, noScope);
+ }
+
+ var scope = ambient as Scope;
+ if (scope == null) throw new Exception();
+
+ return AmbientScope = new Scope(this, scope);
+ }
+
+ public IScope CreateNoScope()
+ {
+ var ambient = AmbientScope;
+ if (ambient != null) throw new Exception();
+ return AmbientScope = new NoScope(this);
+ }
+
+ public void Disposing(IScope disposing, bool? completed = null)
+ {
+ if (disposing != AmbientScope)
+ throw new InvalidOperationException();
+
+ var noScope = disposing as NoScope;
+ if (noScope != null)
+ {
+ // fixme - kinda legacy
+ if (noScope.HasDatabase) noScope.Database.Dispose();
+ AmbientScope = null;
+ return;
+ }
+
+ var scope = disposing as Scope;
+ if (scope == null)
+ throw new Exception();
+
+ var parent = scope.ParentScope;
+ AmbientScope = parent;
+
+ if (parent != null)
+ {
+ parent.CompleteChild(completed);
+ return;
+ }
+
+ // fixme - a scope is in a transaction only if ... there is a db transaction, or always?
+ // what shall we do with events if not in a transaction?
+
+ // note - messages
+ // at the moment we are totally not filtering the messages based on completion
+ // status, so whether the scope is committed or rolled back makes no difference
+
+ if (completed.HasValue && completed.Value)
+ {
+ var database = scope.HasDatabase ? scope.Database : null;
+ if (database != null)
+ {
+ database.CompleteTransaction();
+ }
+ }
+ else
+ {
+ var database = scope.HasDatabase ? scope.Database : null;
+ if (database != null)
+ {
+ database.AbortTransaction();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 950a44502f..3dc2cc31f4 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -512,6 +512,12 @@
+
+
+
+
+
+
diff --git a/src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs b/src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs
index 7e8aa19880..ac7758c9e0 100644
--- a/src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs
+++ b/src/Umbraco.Tests/BootManagers/CoreBootManagerTests.cs
@@ -14,6 +14,7 @@ using Umbraco.Tests.TestHelpers;
using umbraco.interfaces;
using Umbraco.Core.Persistence;
using Umbraco.Core.Profiling;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
namespace Umbraco.Tests.BootManagers
@@ -71,7 +72,7 @@ namespace Umbraco.Tests.BootManagers
{
var appContext = base.CreateApplicationContext(dbContext, serviceContext);
- var dbContextMock = new Mock(Mock.Of(), ProfilingLogger.Logger, Mock.Of(), "test");
+ var dbContextMock = new Mock(Mock.Of(), ProfilingLogger.Logger, Mock.Of(), "test");
dbContextMock.Setup(x => x.CanConnect).Returns(true);
appContext.DatabaseContext = dbContextMock.Object;
diff --git a/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs b/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs
index 6d49205adb..70ae17fa14 100644
--- a/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs
+++ b/src/Umbraco.Tests/Migrations/FindingMigrationsTest.cs
@@ -11,6 +11,7 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Migrations;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Profiling;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Tests.Migrations.Stubs;
@@ -39,7 +40,7 @@ namespace Umbraco.Tests.Migrations
//This is needed because the Migration resolver is creating migration instances with their full ctors
ApplicationContext.EnsureContext(
new ApplicationContext(
- new DatabaseContext(Mock.Of(), Mock.Of(), sqlSyntax, "test"),
+ new DatabaseContext(Mock.Of(), Mock.Of(), sqlSyntax, "test"),
new ServiceContext(),
CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of(), Mock.Of())),
diff --git a/src/Umbraco.Tests/MockTests.cs b/src/Umbraco.Tests/MockTests.cs
index d9a8ef785e..a01c74c763 100644
--- a/src/Umbraco.Tests/MockTests.cs
+++ b/src/Umbraco.Tests/MockTests.cs
@@ -16,6 +16,7 @@ using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Profiling;
using Umbraco.Core.Services;
using Moq;
+using Umbraco.Core.Scoping;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web;
using Umbraco.Web.Routing;
@@ -46,7 +47,7 @@ namespace Umbraco.Tests
[Test]
public void Can_Create_Db_Context()
{
- var dbCtx = new DatabaseContext(new Mock().Object, Mock.Of(), Mock.Of(), "test");
+ var dbCtx = new DatabaseContext(new Mock().Object, Mock.Of(), Mock.Of(), "test");
Assert.Pass();
}
@@ -54,7 +55,7 @@ namespace Umbraco.Tests
public void Can_Create_App_Context_With_Services()
{
var appCtx = new ApplicationContext(
- new DatabaseContext(new Mock().Object, Mock.Of(), Mock.Of(), "test"),
+ new DatabaseContext(new Mock().Object, Mock.Of(), Mock.Of(), "test"),
MockHelper.GetMockedServiceContext(),
CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of(), Mock.Of()));
diff --git a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs
index f0d423cda5..fd7a9997d9 100644
--- a/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs
+++ b/src/Umbraco.Tests/Models/Mapping/ContentTypeModelMappingTests.cs
@@ -13,6 +13,7 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Profiling;
using Umbraco.Core.PropertyEditors;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -40,7 +41,7 @@ namespace Umbraco.Tests.Models.Mapping
//Create an app context using mocks
var appContext = new ApplicationContext(
- new DatabaseContext(Mock.Of(), Mock.Of(), Mock.Of(), "test"),
+ new DatabaseContext(Mock.Of(), Mock.Of(), Mock.Of(), "test"),
//Create service context using mocks
new ServiceContext(
diff --git a/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs b/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs
index e8c994f1c0..f60182fc9f 100644
--- a/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs
+++ b/src/Umbraco.Tests/Persistence/BaseTableByTableTest.cs
@@ -15,6 +15,7 @@ using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Profiling;
using Umbraco.Core.Publishing;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
using GlobalSettings = Umbraco.Core.Configuration.GlobalSettings;
@@ -51,7 +52,7 @@ namespace Umbraco.Tests.Persistence
var dbContext = new DatabaseContext(
- new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, _logger),
+ new ScopeProvider(new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, _logger)),
_logger, SqlSyntaxProvider, Constants.DatabaseProviders.SqlCe);
var repositoryFactory = new RepositoryFactory(cacheHelper, _logger, SqlSyntaxProvider, SettingsForTests.GenerateMockSettings());
diff --git a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs
index bf0d3a736b..7bd4086d9c 100644
--- a/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs
+++ b/src/Umbraco.Tests/Persistence/DatabaseContextTests.cs
@@ -9,6 +9,7 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Profiling;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
@@ -27,7 +28,7 @@ namespace Umbraco.Tests.Persistence
SafeCallContext.Clear();
_dbContext = new DatabaseContext(
- new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, Mock.Of()),
+ new ScopeProvider(new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, Mock.Of())),
Mock.Of(), new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe);
//unfortunately we have to set this up because the PetaPocoExtensions require singleton access
@@ -91,9 +92,10 @@ namespace Umbraco.Tests.Persistence
}
var dbFactory = new DefaultDatabaseFactory(connString, Constants.DatabaseProviders.SqlCe, Mock.Of());
+ var scopeProvider = new ScopeProvider(dbFactory);
//re-map the dbcontext to the new conn string
_dbContext = new DatabaseContext(
- dbFactory,
+ scopeProvider,
Mock.Of(),
new SqlCeSyntaxProvider(),
dbFactory.ProviderName);
diff --git a/src/Umbraco.Tests/Persistence/LocksTests.cs b/src/Umbraco.Tests/Persistence/LocksTests.cs
index 624a94f39f..ae008e1e63 100644
--- a/src/Umbraco.Tests/Persistence/LocksTests.cs
+++ b/src/Umbraco.Tests/Persistence/LocksTests.cs
@@ -11,6 +11,7 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Publishing;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Tests.Services;
using Umbraco.Tests.TestHelpers;
@@ -36,8 +37,9 @@ namespace Umbraco.Tests.Persistence
//threading environment, or a single apartment threading environment will not work for this test because
//it is multi-threaded.
_dbFactory = new ThreadSafetyServiceTest.PerThreadDatabaseFactory(Logger);
+ var scopeProvider = new ScopeProvider(_dbFactory);
//overwrite the local object
- ApplicationContext.DatabaseContext = new DatabaseContext(_dbFactory, Logger, new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe);
+ ApplicationContext.DatabaseContext = new DatabaseContext(scopeProvider, Logger, new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe);
//disable cache
var cacheHelper = CacheHelper.CreateDisabledCacheHelper();
diff --git a/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs b/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs
index be9928004b..85c07972a5 100644
--- a/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs
+++ b/src/Umbraco.Tests/Routing/UrlRoutingTestBase.cs
@@ -8,6 +8,7 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.SqlSyntax;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
@@ -61,7 +62,7 @@ namespace Umbraco.Tests.Routing
{
var settings = SettingsForTests.GetDefault();
return new ApplicationContext(
- new DatabaseContext(Mock.Of(), Logger, Mock.Of(), "test"),
+ new DatabaseContext(Mock.Of(), Logger, Mock.Of(), "test"),
GetServiceContext(settings, Logger),
CacheHelper,
ProfilingLogger)
diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs
index 504dc0d475..2a67a8194c 100644
--- a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs
+++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs
@@ -19,6 +19,7 @@ using Umbraco.Tests.TestHelpers.Entities;
using umbraco.editorControls.tinyMCE3;
using umbraco.interfaces;
using Umbraco.Core.Events;
+using Umbraco.Core.Scoping;
namespace Umbraco.Tests.Services
{
@@ -39,8 +40,9 @@ namespace Umbraco.Tests.Services
//threading environment, or a single apartment threading environment will not work for this test because
//it is multi-threaded.
_dbFactory = new PerThreadDatabaseFactory(Logger);
+ var scopeProvider = new ScopeProvider(_dbFactory);
//overwrite the local object
- ApplicationContext.DatabaseContext = new DatabaseContext(_dbFactory, Logger, new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe);
+ ApplicationContext.DatabaseContext = new DatabaseContext(scopeProvider, Logger, new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe);
//disable cache
var cacheHelper = CacheHelper.CreateDisabledCacheHelper();
@@ -218,7 +220,7 @@ namespace Umbraco.Tests.Services
///
/// Creates a Database object per thread, this mimics the web context which is per HttpContext and is required for the multi-threaded test
///
- internal class PerThreadDatabaseFactory : DisposableObject, IDatabaseFactory
+ internal class PerThreadDatabaseFactory : DisposableObject, IDatabaseFactory2
{
private readonly ILogger _logger;
@@ -237,7 +239,12 @@ namespace Umbraco.Tests.Services
return db;
}
- protected override void DisposeResources()
+ public UmbracoDatabase CreateNewDatabase()
+ {
+ return new UmbracoDatabase(Constants.System.UmbracoConnectionName, _logger);
+ }
+
+ protected override void DisposeResources()
{
//dispose the databases
_databases.ForEach(x => x.Value.Dispose());
@@ -249,26 +256,21 @@ namespace Umbraco.Tests.Services
///
internal class PerThreadPetaPocoUnitOfWorkProvider : DisposableObject, IDatabaseUnitOfWorkProvider
{
- private readonly PerThreadDatabaseFactory _dbFactory;
+ private readonly ScopeProvider _scopeProvider;
public PerThreadPetaPocoUnitOfWorkProvider(PerThreadDatabaseFactory dbFactory)
{
- _dbFactory = dbFactory;
+ _scopeProvider = new ScopeProvider(dbFactory);
}
public IDatabaseUnitOfWork GetUnitOfWork()
{
//Create or get a database instance for this thread.
- var db = _dbFactory.CreateDatabase();
- return new PetaPocoUnitOfWork(db);
+ return new PetaPocoUnitOfWork(_scopeProvider);
}
protected override void DisposeResources()
- {
- //dispose the databases
- _dbFactory.Dispose();
- }
+ { }
}
-
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs
index 873cf6ada4..6bd44c2bdd 100644
--- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs
+++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs
@@ -24,6 +24,7 @@ using Umbraco.Web.PublishedCache.XmlPublishedCache;
using Umbraco.Web.Security;
using umbraco.BusinessLogic;
using Umbraco.Core.Events;
+using Umbraco.Core.Scoping;
namespace Umbraco.Tests.TestHelpers
{
@@ -70,7 +71,6 @@ namespace Umbraco.Tests.TestHelpers
GetDbConnectionString(),
GetDbProviderName(),
Logger);
- _dbFactory.ResetForTests();
base.Initialize();
@@ -92,11 +92,12 @@ namespace Umbraco.Tests.TestHelpers
var repositoryFactory = new RepositoryFactory(CacheHelper, Logger, SqlSyntax, SettingsForTests.GenerateMockSettings());
var evtMsgs = new TransientMessagesFactory();
+ var scopeProvider = new ScopeProvider(_dbFactory);
_appContext = new ApplicationContext(
//assign the db context
- new DatabaseContext(_dbFactory, Logger, SqlSyntax, GetDbProviderName()),
+ new DatabaseContext(scopeProvider, Logger, SqlSyntax, GetDbProviderName()),
//assign the service context
- new ServiceContext(repositoryFactory, new PetaPocoUnitOfWorkProvider(_dbFactory), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, Logger), CacheHelper, Logger, evtMsgs),
+ new ServiceContext(repositoryFactory, new PetaPocoUnitOfWorkProvider(scopeProvider), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, Logger), CacheHelper, Logger, evtMsgs),
CacheHelper,
ProfilingLogger)
{
diff --git a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs
index e0453557fa..723d6ecade 100644
--- a/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs
+++ b/src/Umbraco.Tests/TestHelpers/BaseUmbracoApplicationTest.cs
@@ -18,6 +18,7 @@ using Umbraco.Web;
using Umbraco.Web.Models.Mapping;
using umbraco.BusinessLogic;
using Umbraco.Core.Events;
+using Umbraco.Core.Scoping;
namespace Umbraco.Tests.TestHelpers
{
@@ -164,7 +165,7 @@ namespace Umbraco.Tests.TestHelpers
var evtMsgs = new TransientMessagesFactory();
var applicationContext = new ApplicationContext(
//assign the db context
- new DatabaseContext(new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, Logger), Logger, sqlSyntax, Constants.DatabaseProviders.SqlCe),
+ new DatabaseContext(new ScopeProvider(new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, Logger)), Logger, sqlSyntax, Constants.DatabaseProviders.SqlCe),
//assign the service context
new ServiceContext(repoFactory, new PetaPocoUnitOfWorkProvider(Logger), new FileUnitOfWorkProvider(), new PublishingStrategy(evtMsgs, Logger), CacheHelper, Logger, evtMsgs),
CacheHelper,
diff --git a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs
index 7c5921c67a..5d0ef96e57 100644
--- a/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs
+++ b/src/Umbraco.Tests/Web/Mvc/SurfaceControllerTests.cs
@@ -14,6 +14,7 @@ using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Profiling;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web;
@@ -74,7 +75,7 @@ namespace Umbraco.Tests.Web.Mvc
public void Umbraco_Helper_Not_Null()
{
var appCtx = new ApplicationContext(
- new DatabaseContext(new Mock().Object, Mock.Of(), Mock.Of(), "test"),
+ new DatabaseContext(new Mock().Object, Mock.Of(), Mock.Of(), "test"),
MockHelper.GetMockedServiceContext(),
CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of(), Mock.Of()));
diff --git a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs
index 92c9a25b0f..e90ea23068 100644
--- a/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs
+++ b/src/Umbraco.Tests/Web/Mvc/UmbracoViewPageTests.cs
@@ -16,6 +16,7 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Profiling;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Stubs;
@@ -463,7 +464,7 @@ namespace Umbraco.Tests.Web.Mvc
var svcCtx = GetServiceContext(umbracoSettings, logger);
var appCtx = new ApplicationContext(
- new DatabaseContext(Mock.Of(), logger, Mock.Of(), "test"),
+ new DatabaseContext(Mock.Of(), logger, Mock.Of(), "test"),
svcCtx,
CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(logger, Mock.Of())) { IsReady = true };
diff --git a/src/Umbraco.Web/Scheduling/LogScrubber.cs b/src/Umbraco.Web/Scheduling/LogScrubber.cs
index 794c927f97..187d0c55a2 100644
--- a/src/Umbraco.Web/Scheduling/LogScrubber.cs
+++ b/src/Umbraco.Web/Scheduling/LogScrubber.cs
@@ -77,8 +77,8 @@ namespace Umbraco.Web.Scheduling
return false; // do NOT repeat, going down
}
- // running on a background task, requires a safe database (see UsingSafeDatabase doc)
- using (ApplicationContext.Current.DatabaseContext.UseSafeDatabase())
+ // running on a background task, requires its own (safe) scope
+ using (ApplicationContext.Current.ScopeProvider.CreateScope())
using (DisposableTimer.DebugDuration("Log scrubbing executing", "Log scrubbing complete"))
{
Log.CleanLogs(GetLogScrubbingMaximumAge(_settings));
diff --git a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs
index 5288f2630a..461abc984a 100644
--- a/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs
+++ b/src/Umbraco.Web/Scheduling/ScheduledPublishing.cs
@@ -82,9 +82,9 @@ namespace Umbraco.Web.Scheduling
Content = new StringContent(string.Empty)
};
- // running on a background task, requires a safe database (see UsingSafeDatabase doc)
+ // running on a background task, requires its own (safe) scope
// (GetAuthenticationHeaderValue uses UserService to load the current user, hence requires a database)
- using (ApplicationContext.Current.DatabaseContext.UseSafeDatabase())
+ using (ApplicationContext.Current.ScopeProvider.CreateScope())
{
//pass custom the authorization header
request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext);
diff --git a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs
index 93d3aa2f87..720dc0b20e 100644
--- a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs
+++ b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs
@@ -137,8 +137,8 @@ namespace Umbraco.Web.Strategies
{
try
{
- // running on a background task, requires a safe database (see UsingSafeDatabase doc)
- using (ApplicationContext.Current.DatabaseContext.UseSafeDatabase())
+ // running on a background task, requires its own (safe) scope
+ using (ApplicationContext.Current.ScopeProvider.CreateScope())
{
_svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout);
}
diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs
index bc41c9c877..afdc482f46 100644
--- a/src/Umbraco.Web/WebBootManager.cs
+++ b/src/Umbraco.Web/WebBootManager.cs
@@ -45,6 +45,7 @@ using Umbraco.Core.Cache;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Publishing;
+using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Web.Editors;
using Umbraco.Web.HealthCheck;
@@ -86,15 +87,15 @@ namespace Umbraco.Web
/// Creates and returns the service context for the app
///
///
- ///
+ ///
///
- protected override ServiceContext CreateServiceContext(DatabaseContext dbContext, IDatabaseFactory dbFactory)
+ protected override ServiceContext CreateServiceContext(DatabaseContext dbContext, IScopeProvider scopeProvider)
{
//use a request based messaging factory
var evtMsgs = new RequestLifespanMessagesFactory(new SingletonHttpContextAccessor());
return new ServiceContext(
new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()),
- new PetaPocoUnitOfWorkProvider(dbFactory),
+ new PetaPocoUnitOfWorkProvider(scopeProvider),
new FileUnitOfWorkProvider(),
new PublishingStrategy(evtMsgs, ProfilingLogger.Logger),
ApplicationCache,
diff --git a/src/UmbracoExamine/DataServices/UmbracoContentService.cs b/src/UmbracoExamine/DataServices/UmbracoContentService.cs
index 4d265bf127..cd6acca8eb 100644
--- a/src/UmbracoExamine/DataServices/UmbracoContentService.cs
+++ b/src/UmbracoExamine/DataServices/UmbracoContentService.cs
@@ -57,7 +57,7 @@ namespace UmbracoExamine.DataServices
[Obsolete("This should no longer be used, latest content will be indexed by using the IContentService directly")]
public XDocument GetLatestContentByXPath(string xpath)
{
- using (_applicationContext.DatabaseContext.UseSafeDatabase())
+ using (ApplicationContext.Current.ScopeProvider.CreateScope())
{
var xmlContent = XDocument.Parse("");
var rootContent = _applicationContext.Services.ContentService.GetRootContent();
@@ -79,7 +79,7 @@ namespace UmbracoExamine.DataServices
///
public bool IsProtected(int nodeId, string path)
{
- using (_applicationContext.DatabaseContext.UseSafeDatabase())
+ using (ApplicationContext.Current.ScopeProvider.CreateScope())
{
return _applicationContext.Services.PublicAccessService.IsProtected(path.EnsureEndsWith("," + nodeId));
}
@@ -92,9 +92,9 @@ namespace UmbracoExamine.DataServices
public IEnumerable GetAllUserPropertyNames()
{
- using (_applicationContext.DatabaseContext.UseSafeDatabase())
+ using (ApplicationContext.Current.ScopeProvider.CreateScope())
{
- try
+ try
{
var result = _applicationContext.DatabaseContext.Database.Fetch("select distinct alias from cmsPropertyType order by alias");
return result;