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;