diff --git a/src/SQLCE4Umbraco/SqlCeApplicationBlock.cs b/src/SQLCE4Umbraco/SqlCeApplicationBlock.cs index fd778bbfb3..2dd0f26e90 100644 --- a/src/SQLCE4Umbraco/SqlCeApplicationBlock.cs +++ b/src/SQLCE4Umbraco/SqlCeApplicationBlock.cs @@ -240,7 +240,7 @@ namespace SqlCE4Umbraco { var cmd = trx == null ? new SqlCeCommand(commandText, conn) : new SqlCeCommand(commandText, conn, trx); AttachParameters(cmd, commandParameters); - return cmd.ExecuteReader(CommandBehavior.CloseConnection); + return cmd.ExecuteReader(); } catch { diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index 88c2d65926..4be0787a31 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -422,10 +422,17 @@ namespace Umbraco.Core this.ApplicationCache = null; if (_databaseContext != null) //need to check the internal field here { + if (_databaseContext.ScopeProvider.AmbientScope != null) + { + var scope = _databaseContext.ScopeProvider.AmbientScope; + scope.Dispose(); + } + /* if (DatabaseContext.IsDatabaseConfigured && DatabaseContext.Database != null) { DatabaseContext.Database.Dispose(); - } + } + */ } this.DatabaseContext = null; this.Services = null; diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index ec5607f641..43a755a780 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -113,8 +113,9 @@ namespace Umbraco.Core { get { - var scope = ScopeProvider.AmbientScope; - return scope != null ? scope.Database : ScopeProvider.CreateNoScope().Database; + return ScopeProvider.AmbientOrNoScope.Database; + //var scope = ScopeProvider.AmbientScope; + //return scope != null ? scope.Database : ScopeProvider.CreateNoScope().Database; } } diff --git a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs index 69026962ac..46a2defd66 100644 --- a/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DefaultDatabaseFactory.cs @@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence //private static readonly object Locker = new object(); // bwc imposes a weird x-dependency between database factory and scope provider... - public ScopeProvider ScopeProvider { get; set; } + public IScopeProviderInternal ScopeProvider { get; set; } /// /// Constructor accepting custom connection string @@ -61,8 +61,9 @@ namespace Umbraco.Core.Persistence public UmbracoDatabase CreateDatabase() { - var scope = ScopeProvider.AmbientScope; - return scope != null ? scope.Database : ScopeProvider.CreateNoScope().Database; + return ScopeProvider.AmbientOrNoScope.Database; + //var scope = ScopeProvider.AmbientScope; + //return scope != null ? scope.Database : ScopeProvider.CreateNoScope().Database; /* UmbracoDatabase database; diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index b569b1c45d..ecd5280012 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -332,8 +332,16 @@ namespace Umbraco.Core.Persistence if (_transactionDepth == 1) { OpenSharedConnection(); - _transaction = _sharedConnection.BeginTransaction(isolationLevel); - _transactionCancelled = false; + try + { + _transaction = _sharedConnection.BeginTransaction(isolationLevel); + } + + catch (Exception e) + { + throw; + } + _transactionCancelled = false; OnBeginTransaction(); } else if (isolationLevel > _transaction.IsolationLevel) diff --git a/src/Umbraco.Core/Persistence/Repositories/NotificationsRepository.cs b/src/Umbraco.Core/Persistence/Repositories/NotificationsRepository.cs index 9e6e3cf47c..aa7ebeacc8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/NotificationsRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/NotificationsRepository.cs @@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal class NotificationsRepository + internal class NotificationsRepository : IDisposable { private readonly IDatabaseUnitOfWork _unitOfWork; @@ -102,5 +102,10 @@ namespace Umbraco.Core.Persistence.Repositories _unitOfWork.Database.Insert(dto); return new Notification(dto.NodeId, dto.UserId, dto.Action, nodeType); } + + public void Dispose() + { + _unitOfWork.Dispose(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs index 44a3db9424..46284b1a2d 100644 --- a/src/Umbraco.Core/Persistence/UmbracoDatabase.cs +++ b/src/Umbraco.Core/Persistence/UmbracoDatabase.cs @@ -57,6 +57,20 @@ namespace Umbraco.Core.Persistence /// internal bool EnableSqlTrace { get; set; } + public bool InTransaction { get; private set; } + + public override void OnBeginTransaction() + { + base.OnBeginTransaction(); + InTransaction = true; + } + + public override void OnEndTransaction() + { + base.OnEndTransaction(); + InTransaction = false; + } + #if DEBUG_DATABASES private const bool EnableSqlTraceDefault = true; #else diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs index 6c71614c1a..6499ea29fe 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs @@ -10,15 +10,16 @@ namespace Umbraco.Core.Persistence.UnitOfWork /// internal class PetaPocoUnitOfWork : DisposableObject, IDatabaseUnitOfWork { - private readonly IScope _scope; - - /// - /// Used for testing - /// - internal Guid InstanceId { get; private set; } - - private Guid _key; private readonly Queue _operations = new Queue(); + private readonly IScopeProvider _scopeProvider; + private bool _completeScope = true; // scope is completed by default + private IScope _scope; + private Guid _key; + + /// + /// Used for testing + /// + internal Guid InstanceId { get; private set; } /// /// Creates a new unit of work instance @@ -29,7 +30,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork /// internal PetaPocoUnitOfWork(IScopeProvider scopeProvider) { - _scope = scopeProvider.CreateScope(); + _scopeProvider = scopeProvider; _key = Guid.NewGuid(); InstanceId = Guid.NewGuid(); } @@ -102,34 +103,34 @@ namespace Umbraco.Core.Persistence.UnitOfWork /// internal void Commit(Action transactionCompleting) { - using (var transaction = Database.GetTransaction()) + // this happens in a scope-managed transaction + + // in case anything goes wrong + _completeScope = false; + + while (_operations.Count > 0) { - while (_operations.Count > 0) + var operation = _operations.Dequeue(); + switch (operation.Type) { - var operation = _operations.Dequeue(); - switch (operation.Type) - { - case TransactionType.Insert: - operation.Repository.PersistNewItem(operation.Entity); - break; - case TransactionType.Delete: - operation.Repository.PersistDeletedItem(operation.Entity); - break; - case TransactionType.Update: - operation.Repository.PersistUpdatedItem(operation.Entity); - break; - } + case TransactionType.Insert: + operation.Repository.PersistNewItem(operation.Entity); + break; + case TransactionType.Delete: + operation.Repository.PersistDeletedItem(operation.Entity); + break; + case TransactionType.Update: + operation.Repository.PersistUpdatedItem(operation.Entity); + break; } - - //Execute the callback if there is one - if (transactionCompleting != null) - { - transactionCompleting(Database); - } - - transaction.Complete(); } + if (transactionCompleting != null) + transactionCompleting(Database); + + // all is ok + _completeScope = true; + // Clear everything _operations.Clear(); _key = Guid.NewGuid(); @@ -140,7 +141,16 @@ namespace Umbraco.Core.Persistence.UnitOfWork get { return _key; } } - public UmbracoDatabase Database {get { return _scope.Database; } } + public UmbracoDatabase Database + { + get + { + if (_scope == null) + //throw new InvalidOperationException("Out-of-scope UnitOfWork."); + _scope = _scopeProvider.CreateScope(); + return _scope.Database; + } + } #region Operation @@ -179,7 +189,11 @@ namespace Umbraco.Core.Persistence.UnitOfWork protected override void DisposeResources() { _operations.Clear(); + if (_scope == null) return; + + if (_completeScope) _scope.Complete(); _scope.Dispose(); + _scope = null; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Scoping/IScope.cs b/src/Umbraco.Core/Scoping/IScope.cs index f56fcdd018..e379273f58 100644 --- a/src/Umbraco.Core/Scoping/IScope.cs +++ b/src/Umbraco.Core/Scoping/IScope.cs @@ -4,11 +4,24 @@ using Umbraco.Core.Persistence; namespace Umbraco.Core.Scoping { + /// + /// Represents a scope. + /// public interface IScope : IDisposeOnRequestEnd // implies IDisposable { + /// + /// Gets the scope database. + /// UmbracoDatabase Database { get; } + + /// + /// Gets the scope event messages. + /// IList Messages { get; } + /// + /// Completes the scope. + /// void Complete(); } } diff --git a/src/Umbraco.Core/Scoping/IScopeProvider.cs b/src/Umbraco.Core/Scoping/IScopeProvider.cs index 112a467cc3..e6237b3af9 100644 --- a/src/Umbraco.Core/Scoping/IScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/IScopeProvider.cs @@ -1,10 +1,47 @@ namespace Umbraco.Core.Scoping { + /// + /// Provides scopes. + /// public interface IScopeProvider { + /// + /// Creates an ambient scope. + /// + /// The created ambient scope. + /// + /// The created scope becomes the ambient scope. + /// If an ambient scope already exists, it becomes the parent of the created scope. + /// When the created scope is disposed, the parent scope becomes the ambient scope again. + /// IScope CreateScope(); + + /// + /// Creates a detached scope. + /// + /// A detached scope. + /// + /// A detached scope is not ambient and has no parent. + /// It is meant to be attached by . + /// IScope CreateDetachedScope(); + + /// + /// Attaches a scope. + /// + /// The scope to attach. + /// + /// Only a scope created by can be attached. + /// void AttachScope(IScope scope); + + /// + /// Detaches a scope. + /// + /// The detached scope. + /// + /// Only a scope previously attached by can be detached. + /// IScope DetachScope(); } } diff --git a/src/Umbraco.Core/Scoping/IScopeProviderInternal.cs b/src/Umbraco.Core/Scoping/IScopeProviderInternal.cs index 8726efbfb6..856d30a4da 100644 --- a/src/Umbraco.Core/Scoping/IScopeProviderInternal.cs +++ b/src/Umbraco.Core/Scoping/IScopeProviderInternal.cs @@ -1,8 +1,28 @@ namespace Umbraco.Core.Scoping { + /// + /// Provices scopes. + /// + /// Extends with internal features. internal interface IScopeProviderInternal : IScopeProvider { - IScope AmbientScope { get; set; } - IScope CreateNoScope(); + /// + /// Gets the ambient scope. + /// + IScope AmbientScope { get; } + + // fixme + IScope AmbientOrNoScope { get; } + + /// + /// Creates a instance. + /// + /// The created ambient scope. + /// + /// The created scope becomes the ambient scope. + /// If an ambient scope already exists, throws. + /// The instance can be eventually replaced by a real instance. + /// + //IScope CreateNoScope(); } } diff --git a/src/Umbraco.Core/Scoping/NoScope.cs b/src/Umbraco.Core/Scoping/NoScope.cs index d72d201060..5f67c28d94 100644 --- a/src/Umbraco.Core/Scoping/NoScope.cs +++ b/src/Umbraco.Core/Scoping/NoScope.cs @@ -8,6 +8,7 @@ namespace Umbraco.Core.Scoping internal class NoScope : IScope { private readonly ScopeProvider _scopeProvider; + private bool _disposed; private UmbracoDatabase _database; private IList _messages; @@ -17,18 +18,44 @@ namespace Umbraco.Core.Scoping _scopeProvider = scopeProvider; } - public bool HasDatabase { get { return _database != null; } } + //public bool HasDatabase { get { return _database != null; } } public UmbracoDatabase Database { - get { return _database ?? (_database = _scopeProvider.DatabaseFactory.CreateNewDatabase()); } + get + { + EnsureNotDisposed(); + return _database ?? (_database = _scopeProvider.DatabaseFactory.CreateNewDatabase()); + } } - public bool HasMessages { get { return _messages != null; } } + public UmbracoDatabase DatabaseOrNull + { + get + { + EnsureNotDisposed(); + return _database; + } + } + + //public bool HasMessages { get { return _messages != null; } } public IList Messages { - get { return _messages ?? (_messages = new List()); } + get + { + EnsureNotDisposed(); + return _messages ?? (_messages = new List()); + } + } + + public IList MessagesOrNull + { + get + { + EnsureNotDisposed(); + return _messages; + } } public void Complete() @@ -36,9 +63,17 @@ namespace Umbraco.Core.Scoping throw new NotImplementedException(); } + private void EnsureNotDisposed() + { + if (_disposed) + throw new ObjectDisposedException("this"); + } + public void Dispose() { + EnsureNotDisposed(); _scopeProvider.Disposing(this); + _disposed = true; GC.SuppressFinalize(this); } } diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index 327adec8ca..8956688faa 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -5,77 +5,100 @@ using Umbraco.Core.Persistence; namespace Umbraco.Core.Scoping { + // note - scope is not thread-safe obviously + internal class Scope : IScope { private readonly ScopeProvider _scopeProvider; + private bool _disposed; private bool? _completed; private UmbracoDatabase _database; private IList _messages; + // initializes a new scope public Scope(ScopeProvider scopeProvider, bool detachable = false) { _scopeProvider = scopeProvider; Detachable = detachable; } + // initializes a new scope in a nested scopes chain, with its parent public Scope(ScopeProvider scopeProvider, Scope parent) : this(scopeProvider) { ParentScope = parent; } + // initializes a new scope, replacing a NoScope instance 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(); - } + // steal everything from NoScope + _database = noScope.DatabaseOrNull; + _messages = noScope.MessagesOrNull; + + // make sure the NoScope can be replaced ie not in a transaction + if (_database != null && _database.InTransaction) + throw new Exception("NoScope instance is not free."); } + // a value indicating whether the scope is detachable + // ie whether it was created by CreateDetachedScope public bool Detachable { get; private set; } + // the parent scope (in a nested scopes chain) public Scope ParentScope { get; set; } + // the original scope (when attaching a detachable scope) public Scope OrigScope { get; set; } - public bool HasDatabase - { - get { return ParentScope == null ? _database != null : ParentScope.HasDatabase; } - } + //public bool HasDatabase + //{ + // get { return ParentScope == null ? _database != null : ParentScope.HasDatabase; } + //} + /// public UmbracoDatabase Database { get { + EnsureNotDisposed(); if (ParentScope != null) return ParentScope.Database; if (_database != null) { - if (_database.Connection == null) // stolen from noScope + if (_database.InTransaction == false) // stolen from noScope _database.BeginTransaction(); // a scope implies a transaction, always + // fixme - what-if exception? return _database; } - _database = _scopeProvider.DatabaseFactory.CreateNewDatabase(); - _database.BeginTransaction(); // a scope implies a transaction, always - return _database; + var database = _scopeProvider.DatabaseFactory.CreateNewDatabase(); + database.BeginTransaction(); // a scope implies a transaction, always + // fixme - should dispose db on exception? + return _database = database; } } - public bool HasMessages + public UmbracoDatabase DatabaseOrNull { - get { return ParentScope == null ? _messages != null : ParentScope.HasMessages; } + get + { + EnsureNotDisposed(); + return ParentScope == null ? _database : ParentScope.DatabaseOrNull; + } } + //public bool HasMessages + //{ + // get { return ParentScope == null ? _messages != null : ParentScope.HasMessages; } + //} + + /// public IList Messages { get { + EnsureNotDisposed(); if (ParentScope != null) return ParentScope.Messages; if (_messages == null) _messages = new List(); @@ -83,6 +106,16 @@ namespace Umbraco.Core.Scoping } } + public IList MessagesOrNull + { + get + { + EnsureNotDisposed(); + return ParentScope == null ? _messages : ParentScope.MessagesOrNull; + } + } + + /// public void Complete() { if (_completed.HasValue == false) @@ -111,9 +144,17 @@ namespace Umbraco.Core.Scoping } } + private void EnsureNotDisposed() + { + if (_disposed) + throw new ObjectDisposedException("this"); + } + public void Dispose() { + EnsureNotDisposed(); _scopeProvider.Disposing(this, _completed); + _disposed = true; GC.SuppressFinalize(this); } } diff --git a/src/Umbraco.Core/Scoping/ScopeProvider.cs b/src/Umbraco.Core/Scoping/ScopeProvider.cs index f3b139827c..c2b918f812 100644 --- a/src/Umbraco.Core/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/ScopeProvider.cs @@ -66,31 +66,29 @@ namespace Umbraco.Core.Scoping } } + /// 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 AmbientOrNoScope + { + get + { + return AmbientScope ?? (AmbientScope = new NoScope(this)); + } + } + /// public IScope CreateDetachedScope() { return new Scope(this, true); } + /// public void AttachScope(IScope other) { var otherScope = other as Scope; @@ -119,6 +117,7 @@ namespace Umbraco.Core.Scoping AmbientScope = otherScope; } + /// public IScope DetachScope() { var ambient = AmbientScope; @@ -141,17 +140,20 @@ namespace Umbraco.Core.Scoping return scope; } + /// public IScope CreateScope() { var ambient = AmbientScope; if (ambient == null) return AmbientScope = new Scope(this); + // replace noScope with a real one 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) + var database = noScope.DatabaseOrNull; + if (database != null && database.InTransaction) throw new Exception(); return AmbientScope = new Scope(this, noScope); } @@ -162,13 +164,6 @@ namespace Umbraco.Core.Scoping 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) @@ -178,7 +173,13 @@ namespace Umbraco.Core.Scoping if (noScope != null) { // fixme - kinda legacy - if (noScope.HasDatabase) noScope.Database.Dispose(); + var noScopeDatabase = noScope.DatabaseOrNull; + if (noScopeDatabase != null) + { + if (noScopeDatabase.InTransaction) + throw new Exception(); + noScopeDatabase.Dispose(); + } AmbientScope = null; return; } @@ -198,27 +199,19 @@ namespace Umbraco.Core.Scoping // 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? + // fixme - when completing... the db should be released, no need to dispose the db? // 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 + var database = scope.DatabaseOrNull; + if (database == null) return; + if (completed.HasValue && completed.Value) - { - var database = scope.HasDatabase ? scope.Database : null; - if (database != null) - { - database.CompleteTransaction(); - } - } + database.CompleteTransaction(); else - { - var database = scope.HasDatabase ? scope.Database : null; - if (database != null) - { - database.AbortTransaction(); - } - } + database.AbortTransaction(); } } } diff --git a/src/Umbraco.Tests/ApplicationContextTests.cs b/src/Umbraco.Tests/ApplicationContextTests.cs index 61dabf6e3b..255093f325 100644 --- a/src/Umbraco.Tests/ApplicationContextTests.cs +++ b/src/Umbraco.Tests/ApplicationContextTests.cs @@ -10,6 +10,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; namespace Umbraco.Tests @@ -26,7 +27,7 @@ namespace Umbraco.Tests migrationEntryService.Setup(x => x.FindEntry(It.IsAny(), It.IsAny())) .Returns(Mock.Of()); - var dbCtx = new Mock(Mock.Of(), Mock.Of(), new SqlCeSyntaxProvider(), "test"); + var dbCtx = new Mock(Mock.Of(), Mock.Of(), new SqlCeSyntaxProvider(), "test"); dbCtx.Setup(x => x.IsDatabaseConfigured).Returns(true); dbCtx.Setup(x => x.CanConnect).Returns(true); @@ -48,7 +49,7 @@ namespace Umbraco.Tests migrationEntryService.Setup(x => x.FindEntry(It.IsAny(), It.IsAny())) .Returns((IMigrationEntry)null); - var dbCtx = new Mock(Mock.Of(), Mock.Of(), new SqlCeSyntaxProvider(), "test"); + var dbCtx = new Mock(Mock.Of(), Mock.Of(), new SqlCeSyntaxProvider(), "test"); dbCtx.Setup(x => x.IsDatabaseConfigured).Returns(true); dbCtx.Setup(x => x.CanConnect).Returns(true); @@ -68,7 +69,7 @@ namespace Umbraco.Tests var migrationEntryService = new Mock(); - var dbCtx = new Mock(Mock.Of(), Mock.Of(), new SqlCeSyntaxProvider(), "test"); + var dbCtx = new Mock(Mock.Of(), Mock.Of(), new SqlCeSyntaxProvider(), "test"); dbCtx.Setup(x => x.IsDatabaseConfigured).Returns(true); dbCtx.Setup(x => x.CanConnect).Returns(true); @@ -88,7 +89,7 @@ namespace Umbraco.Tests var migrationEntryService = new Mock(); - var dbCtx = new Mock(Mock.Of(), Mock.Of(), new SqlCeSyntaxProvider(), "test"); + var dbCtx = new Mock(Mock.Of(), Mock.Of(), new SqlCeSyntaxProvider(), "test"); dbCtx.Setup(x => x.IsDatabaseConfigured).Returns(false); dbCtx.Setup(x => x.CanConnect).Returns(true); @@ -108,7 +109,7 @@ namespace Umbraco.Tests var migrationEntryService = new Mock(); - var dbCtx = new Mock(Mock.Of(), Mock.Of(), new SqlCeSyntaxProvider(), "test"); + var dbCtx = new Mock(Mock.Of(), Mock.Of(), new SqlCeSyntaxProvider(), "test"); dbCtx.Setup(x => x.IsDatabaseConfigured).Returns(true); dbCtx.Setup(x => x.CanConnect).Returns(false); diff --git a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs index 32bbd34a77..f3af12279c 100644 --- a/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs +++ b/src/Umbraco.Tests/Cache/PublishedCache/PublishedMediaCacheTests.cs @@ -25,7 +25,7 @@ namespace Umbraco.Tests.Cache.PublishedCache PublishedContentModelFactoryResolver.Current = new PublishedContentModelFactoryResolver(); base.FreezeResolution(); } - + [Test] public void Get_Root_Docs() { @@ -35,7 +35,7 @@ namespace Umbraco.Tests.Cache.PublishedCache var mRoot2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("MediaRoot2", mType, user, -1); var mChild1 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child1", mType, user, mRoot1.Id); var mChild2 = global::umbraco.cms.businesslogic.media.Media.MakeNew("Child2", mType, user, mRoot2.Id); - + var ctx = GetUmbracoContext("/test", 1234); var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application), ctx); var roots = cache.GetAtRoot(); @@ -125,7 +125,7 @@ namespace Umbraco.Tests.Cache.PublishedCache { child1, child2 }); - + Assert.AreEqual(2, dicDoc.Children.Count()); Assert.AreEqual(222333, dicDoc.Children.ElementAt(0).Id); Assert.AreEqual(444555, dicDoc.Children.ElementAt(1).Id); @@ -187,7 +187,7 @@ namespace Umbraco.Tests.Cache.PublishedCache private XmlDocument GetMediaXml() { var xml = @" - @@ -195,12 +195,12 @@ namespace Umbraco.Tests.Cache.PublishedCache ]> - + - + - + "; @@ -210,8 +210,8 @@ namespace Umbraco.Tests.Cache.PublishedCache return xmlDoc; } - private Dictionary GetDictionary( - int id, + private Dictionary GetDictionary( + int id, Guid key, int parentId, string idKey, @@ -241,13 +241,13 @@ namespace Umbraco.Tests.Cache.PublishedCache {"parentID", parentId.ToString()} }; } - + private PublishedMediaCache.DictionaryPublishedContent GetDictionaryDocument( string idKey = "id", string templateKey = "template", string nodeNameKey = "nodeName", string nodeTypeAliasKey = "nodeTypeAlias", - string pathKey = "path", + string pathKey = "path", int idVal = 1234, Guid keyVal = default(Guid), int parentIdVal = 321, @@ -265,12 +265,12 @@ namespace Umbraco.Tests.Cache.PublishedCache a => null, //we're not going to test this so ignore (dd, n) => new List(), - (dd, a) => dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(a)), + (dd, a) => dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(a)), null, false), //callback to get the children (dd, n) => children, - (dd, a) => dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(a)), + (dd, a) => dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(a)), null, false); return dicDoc; @@ -300,7 +300,7 @@ namespace Umbraco.Tests.Cache.PublishedCache if (!updateDateVal.HasValue) updateDateVal = DateTime.Parse("2012-01-03"); - DoAssert((IPublishedContent)dicDoc, idVal, keyVal, templateIdVal, sortOrderVal, urlNameVal, nodeTypeAliasVal, nodeTypeIdVal, writerNameVal, + DoAssert((IPublishedContent)dicDoc, idVal, keyVal, templateIdVal, sortOrderVal, urlNameVal, nodeTypeAliasVal, nodeTypeIdVal, writerNameVal, creatorNameVal, writerIdVal, creatorIdVal, pathVal, createDateVal, updateDateVal, levelVal); //now validate the parentId that has been parsed, this doesn't exist on the IPublishedContent @@ -345,9 +345,9 @@ namespace Umbraco.Tests.Cache.PublishedCache Assert.AreEqual(createDateVal.Value, doc.CreateDate); Assert.AreEqual(updateDateVal.Value, doc.UpdateDate); Assert.AreEqual(levelVal, doc.Level); - + } - + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs index 54387a03f9..f2aa367ffd 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/NotificationsRepositoryTest.cs @@ -22,19 +22,20 @@ namespace Umbraco.Tests.Persistence.Repositories { var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - var repo = new NotificationsRepository(unitOfWork); + using (var repo = new NotificationsRepository(unitOfWork)) + { + var node = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1,123", SortOrder = 1, Text = "hello", Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0 }; + var result = unitOfWork.Database.Insert(node); + var entity = Mock.Of(e => e.Id == node.NodeId); + var user = Mock.Of(e => e.Id == node.UserId); - var node = new NodeDto {CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1,123", SortOrder = 1, Text = "hello", Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0}; - var result = unitOfWork.Database.Insert(node); - var entity = Mock.Of(e => e.Id == node.NodeId); - var user = Mock.Of(e => e.Id == node.UserId); + var notification = repo.CreateNotification(user, entity, "A"); - var notification = repo.CreateNotification(user, entity, "A"); - - Assert.AreEqual("A", notification.Action); - Assert.AreEqual(node.NodeId, notification.EntityId); - Assert.AreEqual(node.NodeObjectType, notification.EntityType); - Assert.AreEqual(node.UserId, notification.UserId); + Assert.AreEqual("A", notification.Action); + Assert.AreEqual(node.NodeId, notification.EntityId); + Assert.AreEqual(node.NodeObjectType, notification.EntityType); + Assert.AreEqual(node.UserId, notification.UserId); + } } [Test] @@ -42,25 +43,26 @@ namespace Umbraco.Tests.Persistence.Repositories { var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - var repo = new NotificationsRepository(unitOfWork); - - var userDto = new UserDto { ContentStartId = -1, Email = "test" , Login = "test" , MediaStartId = -1, Password = "test" , Type = 1, UserName = "test" , UserLanguage = "en" }; - unitOfWork.Database.Insert(userDto); - - var userNew = Mock.Of(e => e.Id == userDto.Id); - var userAdmin = Mock.Of(e => e.Id == 0); - - for (var i = 0; i < 10; i++) + using (var repo = new NotificationsRepository(unitOfWork)) { - var node = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1," + i, SortOrder = 1, Text = "hello" + i, Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0 }; - var result = unitOfWork.Database.Insert(node); - var entity = Mock.Of(e => e.Id == node.NodeId); - var notification = repo.CreateNotification((i % 2 == 0) ? userAdmin : userNew, entity, i.ToString(CultureInfo.InvariantCulture)); + var userDto = new UserDto { ContentStartId = -1, Email = "test", Login = "test", MediaStartId = -1, Password = "test", Type = 1, UserName = "test", UserLanguage = "en" }; + unitOfWork.Database.Insert(userDto); + + var userNew = Mock.Of(e => e.Id == userDto.Id); + var userAdmin = Mock.Of(e => e.Id == 0); + + for (var i = 0; i < 10; i++) + { + var node = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1," + i, SortOrder = 1, Text = "hello" + i, Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0 }; + var result = unitOfWork.Database.Insert(node); + var entity = Mock.Of(e => e.Id == node.NodeId); + var notification = repo.CreateNotification((i % 2 == 0) ? userAdmin : userNew, entity, i.ToString(CultureInfo.InvariantCulture)); + } + + var notifications = repo.GetUserNotifications(userAdmin); + + Assert.AreEqual(5, notifications.Count()); } - - var notifications = repo.GetUserNotifications(userAdmin); - - Assert.AreEqual(5, notifications.Count()); } [Test] @@ -68,26 +70,27 @@ namespace Umbraco.Tests.Persistence.Repositories { var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - var repo = new NotificationsRepository(unitOfWork); - - var node1 = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1,1", SortOrder = 1, Text = "hello1", Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0 }; - unitOfWork.Database.Insert(node1); - var entity1 = Mock.Of(e => e.Id == node1.NodeId); - var node2 = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1,2", SortOrder = 1, Text = "hello2", Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0 }; - unitOfWork.Database.Insert(node2); - var entity2 = Mock.Of(e => e.Id == node2.NodeId); - - for (var i = 0; i < 10; i++) + using (var repo = new NotificationsRepository(unitOfWork)) { - var userDto = new UserDto { ContentStartId = -1, Email = "test" + i, Login = "test" + i, MediaStartId = -1, Password = "test", Type = 1, UserName = "test" + i, UserLanguage = "en" }; - unitOfWork.Database.Insert(userDto); - var userNew = Mock.Of(e => e.Id == userDto.Id); - var notification = repo.CreateNotification(userNew, (i % 2 == 0) ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); + var node1 = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1,1", SortOrder = 1, Text = "hello1", Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0 }; + unitOfWork.Database.Insert(node1); + var entity1 = Mock.Of(e => e.Id == node1.NodeId); + var node2 = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1,2", SortOrder = 1, Text = "hello2", Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0 }; + unitOfWork.Database.Insert(node2); + var entity2 = Mock.Of(e => e.Id == node2.NodeId); + + for (var i = 0; i < 10; i++) + { + var userDto = new UserDto { ContentStartId = -1, Email = "test" + i, Login = "test" + i, MediaStartId = -1, Password = "test", Type = 1, UserName = "test" + i, UserLanguage = "en" }; + unitOfWork.Database.Insert(userDto); + var userNew = Mock.Of(e => e.Id == userDto.Id); + var notification = repo.CreateNotification(userNew, (i % 2 == 0) ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); + } + + var notifications = repo.GetEntityNotifications(entity1); + + Assert.AreEqual(5, notifications.Count()); } - - var notifications = repo.GetEntityNotifications(entity1); - - Assert.AreEqual(5, notifications.Count()); } [Test] @@ -95,26 +98,27 @@ namespace Umbraco.Tests.Persistence.Repositories { var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - var repo = new NotificationsRepository(unitOfWork); - - var node1 = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1,1", SortOrder = 1, Text = "hello1", Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0 }; - unitOfWork.Database.Insert(node1); - var entity1 = Mock.Of(e => e.Id == node1.NodeId); - var node2 = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1,2", SortOrder = 1, Text = "hello2", Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0 }; - unitOfWork.Database.Insert(node2); - var entity2 = Mock.Of(e => e.Id == node2.NodeId); - - for (var i = 0; i < 10; i++) + using (var repo = new NotificationsRepository(unitOfWork)) { - var userDto = new UserDto { ContentStartId = -1, Email = "test" + i, Login = "test" + i, MediaStartId = -1, Password = "test", Type = 1, UserName = "test" + i, UserLanguage = "en" }; - unitOfWork.Database.Insert(userDto); - var userNew = Mock.Of(e => e.Id == userDto.Id); - var notification = repo.CreateNotification(userNew, (i % 2 == 0) ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); + var node1 = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1,1", SortOrder = 1, Text = "hello1", Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0 }; + unitOfWork.Database.Insert(node1); + var entity1 = Mock.Of(e => e.Id == node1.NodeId); + var node2 = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1,2", SortOrder = 1, Text = "hello2", Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0 }; + unitOfWork.Database.Insert(node2); + var entity2 = Mock.Of(e => e.Id == node2.NodeId); + + for (var i = 0; i < 10; i++) + { + var userDto = new UserDto { ContentStartId = -1, Email = "test" + i, Login = "test" + i, MediaStartId = -1, Password = "test", Type = 1, UserName = "test" + i, UserLanguage = "en" }; + unitOfWork.Database.Insert(userDto); + var userNew = Mock.Of(e => e.Id == userDto.Id); + var notification = repo.CreateNotification(userNew, (i % 2 == 0) ? entity1 : entity2, i.ToString(CultureInfo.InvariantCulture)); + } + + var delCount = repo.DeleteNotifications(entity1); + + Assert.AreEqual(5, delCount); } - - var delCount = repo.DeleteNotifications(entity1); - - Assert.AreEqual(5, delCount); } [Test] @@ -122,25 +126,26 @@ namespace Umbraco.Tests.Persistence.Repositories { var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - var repo = new NotificationsRepository(unitOfWork); - - var userDto = new UserDto { ContentStartId = -1, Email = "test", Login = "test", MediaStartId = -1, Password = "test", Type = 1, UserName = "test", UserLanguage = "en" }; - unitOfWork.Database.Insert(userDto); - - var userNew = Mock.Of(e => e.Id == userDto.Id); - var userAdmin = Mock.Of(e => e.Id == 0); - - for (var i = 0; i < 10; i++) + using (var repo = new NotificationsRepository(unitOfWork)) { - var node = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1," + i, SortOrder = 1, Text = "hello" + i, Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0 }; - var result = unitOfWork.Database.Insert(node); - var entity = Mock.Of(e => e.Id == node.NodeId); - var notification = repo.CreateNotification((i % 2 == 0) ? userAdmin : userNew, entity, i.ToString(CultureInfo.InvariantCulture)); + var userDto = new UserDto { ContentStartId = -1, Email = "test", Login = "test", MediaStartId = -1, Password = "test", Type = 1, UserName = "test", UserLanguage = "en" }; + unitOfWork.Database.Insert(userDto); + + var userNew = Mock.Of(e => e.Id == userDto.Id); + var userAdmin = Mock.Of(e => e.Id == 0); + + for (var i = 0; i < 10; i++) + { + var node = new NodeDto { CreateDate = DateTime.Now, Level = 1, NodeObjectType = Guid.Parse(Constants.ObjectTypes.ContentItem), ParentId = -1, Path = "-1," + i, SortOrder = 1, Text = "hello" + i, Trashed = false, UniqueId = Guid.NewGuid(), UserId = 0 }; + var result = unitOfWork.Database.Insert(node); + var entity = Mock.Of(e => e.Id == node.NodeId); + var notification = repo.CreateNotification((i % 2 == 0) ? userAdmin : userNew, entity, i.ToString(CultureInfo.InvariantCulture)); + } + + var delCount = repo.DeleteNotifications(userAdmin); + + Assert.AreEqual(5, delCount); } - - var delCount = repo.DeleteNotifications(userAdmin); - - Assert.AreEqual(5, delCount); } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Scoping/ScopeTests.cs b/src/Umbraco.Tests/Scoping/ScopeTests.cs new file mode 100644 index 0000000000..d84b4fc554 --- /dev/null +++ b/src/Umbraco.Tests/Scoping/ScopeTests.cs @@ -0,0 +1,391 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Persistence; +using Umbraco.Core.Scoping; +using Umbraco.Tests.TestHelpers; + +namespace Umbraco.Tests.Scoping +{ + [TestFixture] + [DatabaseTestBehavior(DatabaseBehavior.EmptyDbFilePerTest)] + public class ScopeTests : BaseDatabaseFactoryTest + { + // setup + public override void Initialize() + { + base.Initialize(); + + // initialization leaves a NoScope around, remove it + var scope = DatabaseContext.ScopeProvider.AmbientScope; + Assert.IsNotNull(scope); + Assert.IsInstanceOf(scope); + scope.Dispose(); + Assert.IsNull(DatabaseContext.ScopeProvider.AmbientScope); // gone + } + + [Test] + public void SimpleCreateScope() + { + var scopeProvider = DatabaseContext.ScopeProvider; + + Assert.IsNull(scopeProvider.AmbientScope); + using (var scope = scopeProvider.CreateScope()) + { + Assert.IsInstanceOf(scope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + } + Assert.IsNull(scopeProvider.AmbientScope); + } + + [Test] + public void SimpleCreateScopeDatabase() + { + var scopeProvider = DatabaseContext.ScopeProvider; + + UmbracoDatabase database; + + Assert.IsNull(scopeProvider.AmbientScope); + using (var scope = scopeProvider.CreateScope()) + { + Assert.IsInstanceOf(scope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + database = scope.Database; // populates scope's database + Assert.IsNotNull(database); + Assert.IsNotNull(database.Connection); // in a transaction + } + Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNull(database.Connection); // poof gone + } + + [Test] + public void NestedCreateScope() + { + var scopeProvider = DatabaseContext.ScopeProvider; + + Assert.IsNull(scopeProvider.AmbientScope); + using (var scope = scopeProvider.CreateScope()) + { + Assert.IsInstanceOf(scope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + using (var nested = scopeProvider.CreateScope()) + { + Assert.IsInstanceOf(nested); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(nested, scopeProvider.AmbientScope); + Assert.AreSame(scope, ((Scope) nested).ParentScope); + } + } + Assert.IsNull(scopeProvider.AmbientScope); + } + + [Test] + public void NestedCreateScopeDatabase() + { + var scopeProvider = DatabaseContext.ScopeProvider; + + UmbracoDatabase database; + + Assert.IsNull(scopeProvider.AmbientScope); + using (var scope = scopeProvider.CreateScope()) + { + Assert.IsInstanceOf(scope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + database = scope.Database; // populates scope's database + Assert.IsNotNull(database); + Assert.IsNotNull(database.Connection); // in a transaction + using (var nested = scopeProvider.CreateScope()) + { + Assert.IsInstanceOf(nested); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(nested, scopeProvider.AmbientScope); + Assert.AreSame(scope, ((Scope)nested).ParentScope); + Assert.AreSame(database, nested.Database); + } + Assert.IsNotNull(database.Connection); // still + } + Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNull(database.Connection); // poof gone + } + + [Test] + public void SimpleNoScope() + { + var scopeProvider = DatabaseContext.ScopeProvider; + + Assert.IsNull(scopeProvider.AmbientScope); + using (var scope = scopeProvider.AmbientOrNoScope) + { + Assert.IsInstanceOf(scope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + } + Assert.IsNull(scopeProvider.AmbientScope); + } + + [Test] + public void SimpleNoScopeDatabase() + { + var scopeProvider = DatabaseContext.ScopeProvider; + + UmbracoDatabase database; + + Assert.IsNull(scopeProvider.AmbientScope); + using (var scope = scopeProvider.AmbientOrNoScope) + { + Assert.IsInstanceOf(scope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + database = scope.Database; // populates scope's database + Assert.IsNotNull(database); + Assert.IsNull(database.Connection); // no transaction + } + Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNull(database.Connection); // still + } + + [Test] + public void NestedNoScope() + { + var scopeProvider = DatabaseContext.ScopeProvider; + + Assert.IsNull(scopeProvider.AmbientScope); + var scope = scopeProvider.AmbientOrNoScope; + Assert.IsInstanceOf(scope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + + using (var nested = scopeProvider.CreateScope()) + { + Assert.IsInstanceOf(nested); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(nested, scopeProvider.AmbientScope); + + // nested does not have a parent + Assert.IsNull(((Scope) nested).ParentScope); + } + + // and when nested is gone, scope is gone + Assert.IsNull(scopeProvider.AmbientScope); + } + + [Test] + public void NestedNoScopeDatabase() + { + var scopeProvider = DatabaseContext.ScopeProvider; + + Assert.IsNull(scopeProvider.AmbientScope); + var scope = scopeProvider.AmbientOrNoScope; + Assert.IsInstanceOf(scope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + var database = scope.Database; + Assert.IsNotNull(database); + Assert.IsNull(database.Connection); // no transaction + + using (var nested = scopeProvider.CreateScope()) + { + Assert.IsInstanceOf(nested); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(nested, scopeProvider.AmbientScope); + var nestedDatabase = nested.Database; // causes transaction + Assert.AreSame(database, nestedDatabase); // stolen + Assert.IsNotNull(database.Connection); // no more + + // nested does not have a parent + Assert.IsNull(((Scope)nested).ParentScope); + } + + // and when nested is gone, scope is gone + Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNull(database.Connection); // poof gone + } + + [Test] + public void NestedNoScopeFail() + { + var scopeProvider = DatabaseContext.ScopeProvider; + + Assert.IsNull(scopeProvider.AmbientScope); + var scope = scopeProvider.AmbientOrNoScope; + Assert.IsInstanceOf(scope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + var database = scope.Database; + Assert.IsNotNull(database); + Assert.IsNull(database.Connection); // no transaction + database.BeginTransaction(); + Assert.IsNotNull(database.Connection); // now there is one + + Assert.Throws(() => + { + // could not steal the database + /*var nested =*/ scopeProvider.CreateScope(); + }); + + // cleanup + database.CompleteTransaction(); + } + + [Test] + public void NoScopeNested() + { + var scopeProvider = DatabaseContext.ScopeProvider; + + Assert.IsNull(scopeProvider.AmbientScope); + using (var scope = scopeProvider.CreateScope()) + { + Assert.IsInstanceOf(scope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + + // AmbientOrNoScope returns the ambient scope + Assert.AreSame(scope, scopeProvider.AmbientOrNoScope); + } + } + + [Test] + public void Transaction() + { + var scopeProvider = DatabaseContext.ScopeProvider; + var noScope = scopeProvider.AmbientOrNoScope; + var database = noScope.Database; + database.Execute("CREATE TABLE tmp (id INT, name NVARCHAR(64))"); + + using (var scope = scopeProvider.CreateScope()) + { + scope.Database.Execute("INSERT INTO tmp (id, name) VALUES (1, 'a')"); + var n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=1"); + Assert.AreEqual("a", n); + } + + using (var scope = scopeProvider.CreateScope()) + { + var n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=1"); + Assert.IsNull(n); + } + + using (var scope = scopeProvider.CreateScope()) + { + scope.Database.Execute("INSERT INTO tmp (id, name) VALUES (1, 'a')"); + scope.Complete(); + } + + using (var scope = scopeProvider.CreateScope()) + { + var n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=1"); + Assert.AreEqual("a", n); + } + } + + [Test] + public void NestedTransactionInnerFail() + { + var scopeProvider = DatabaseContext.ScopeProvider; + var noScope = scopeProvider.AmbientOrNoScope; + var database = noScope.Database; + database.Execute("CREATE TABLE tmp (id INT, name NVARCHAR(64))"); + + using (var scope = scopeProvider.CreateScope()) + { + scope.Database.Execute("INSERT INTO tmp (id, name) VALUES (1, 'a')"); + var n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=1"); + Assert.AreEqual("a", n); + + using (var nested = scopeProvider.CreateScope()) + { + nested.Database.Execute("INSERT INTO tmp (id, name) VALUES (2, 'b')"); + var nn = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); + Assert.AreEqual("b", nn); + } + + n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); + Assert.AreEqual("b", n); + + scope.Complete(); + } + + using (var scope = scopeProvider.CreateScope()) + { + var n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=1"); + Assert.IsNull(n); + n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); + Assert.IsNull(n); + } + } + + [Test] + public void NestedTransactionOuterFail() + { + var scopeProvider = DatabaseContext.ScopeProvider; + var noScope = scopeProvider.AmbientOrNoScope; + var database = noScope.Database; + database.Execute("CREATE TABLE tmp (id INT, name NVARCHAR(64))"); + + using (var scope = scopeProvider.CreateScope()) + { + scope.Database.Execute("INSERT INTO tmp (id, name) VALUES (1, 'a')"); + var n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=1"); + Assert.AreEqual("a", n); + + using (var nested = scopeProvider.CreateScope()) + { + nested.Database.Execute("INSERT INTO tmp (id, name) VALUES (2, 'b')"); + var nn = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); + Assert.AreEqual("b", nn); + nested.Complete(); + } + + n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); + Assert.AreEqual("b", n); + } + + using (var scope = scopeProvider.CreateScope()) + { + var n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=1"); + Assert.IsNull(n); + n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); + Assert.IsNull(n); + } + } + + [Test] + public void NestedTransactionComplete() + { + var scopeProvider = DatabaseContext.ScopeProvider; + var noScope = scopeProvider.AmbientOrNoScope; + var database = noScope.Database; + database.Execute("CREATE TABLE tmp (id INT, name NVARCHAR(64))"); + + using (var scope = scopeProvider.CreateScope()) + { + scope.Database.Execute("INSERT INTO tmp (id, name) VALUES (1, 'a')"); + var n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=1"); + Assert.AreEqual("a", n); + + using (var nested = scopeProvider.CreateScope()) + { + nested.Database.Execute("INSERT INTO tmp (id, name) VALUES (2, 'b')"); + var nn = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); + Assert.AreEqual("b", nn); + nested.Complete(); + } + + n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); + Assert.AreEqual("b", n); + scope.Complete(); + } + + using (var scope = scopeProvider.CreateScope()) + { + var n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=1"); + Assert.AreEqual("a", n); + n = scope.Database.ExecuteScalar("SELECT name FROM tmp WHERE id=2"); + Assert.AreEqual("b", n); + } + } + } +} diff --git a/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs b/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs index 320ab400d3..92250487b1 100644 --- a/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs +++ b/src/Umbraco.Tests/Security/BackOfficeCookieManagerTests.cs @@ -11,6 +11,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Profiling; +using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; using Umbraco.Web; using Umbraco.Web.Routing; @@ -28,7 +29,7 @@ namespace Umbraco.Tests.Security //should force app ctx to show not-configured ConfigurationManager.AppSettings.Set("umbracoConfigurationStatus", ""); - var dbCtx = new Mock(Mock.Of(), Mock.Of(), Mock.Of(), "test"); + var dbCtx = new Mock(Mock.Of(), Mock.Of(), Mock.Of(), "test"); dbCtx.Setup(x => x.IsDatabaseConfigured).Returns(false); var appCtx = new ApplicationContext( @@ -53,7 +54,7 @@ namespace Umbraco.Tests.Security [Test] public void ShouldAuthenticateRequest_When_Configured() { - var dbCtx = new Mock(Mock.Of(), Mock.Of(), Mock.Of(), "test"); + var dbCtx = new Mock(Mock.Of(), Mock.Of(), Mock.Of(), "test"); dbCtx.Setup(x => x.IsDatabaseConfigured).Returns(true); var appCtx = new ApplicationContext( diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 72a58861e2..46af227ecd 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -81,7 +81,7 @@ namespace Umbraco.Tests.Services Assert.IsTrue(moveResult.Success); - //re-get with the fixed/moved path + //re-get with the fixed/moved path content = contentService.GetById(content.Id); Assert.AreEqual("-1,-20," + content.Id, content.Path); @@ -270,7 +270,7 @@ namespace Umbraco.Tests.Services content2.SetTags("tags", new[] { "hello", "world", "some", "tags" }, true); contentService.Publish(content2); - // Act + // Act contentService.MoveToRecycleBin(content1); // Assert @@ -306,7 +306,7 @@ namespace Umbraco.Tests.Services content2.SetTags("tags", new[] { "hello", "world", "some", "tags" }, true); contentService.Publish(content2); - // Act + // Act contentService.MoveToRecycleBin(content1); contentService.MoveToRecycleBin(content2); @@ -339,7 +339,7 @@ namespace Umbraco.Tests.Services content2.SetTags("tags", new[] { "hello", "world", "some", "tags" }, true); contentService.Publish(content2); - // Act + // Act contentService.UnPublish(content1); contentService.UnPublish(content2); @@ -375,7 +375,7 @@ namespace Umbraco.Tests.Services contentService.UnPublish(content1); contentService.UnPublish(content2); - // Act + // Act contentService.Publish(content1); // Assert @@ -411,7 +411,7 @@ namespace Umbraco.Tests.Services contentService.MoveToRecycleBin(content1); contentService.MoveToRecycleBin(content2); - // Act + // Act contentService.Move(content1, -1); contentService.Publish(content1); @@ -839,8 +839,10 @@ namespace Umbraco.Tests.Services bool published = contentService.Publish(content, 0); var provider = new PetaPocoUnitOfWorkProvider(Logger); - var uow = provider.GetUnitOfWork(); - Assert.IsTrue(uow.Database.Exists(content.Id)); + using (var uow = provider.GetUnitOfWork()) + { + Assert.IsTrue(uow.Database.Exists(content.Id)); + } // Act bool unpublished = contentService.UnPublish(content, 0); @@ -850,8 +852,10 @@ namespace Umbraco.Tests.Services Assert.That(unpublished, Is.True); Assert.That(content.Published, Is.False); - uow = provider.GetUnitOfWork(); - Assert.IsFalse(uow.Database.Exists(content.Id)); + using (var uow = provider.GetUnitOfWork()) + { + Assert.IsFalse(uow.Database.Exists(content.Id)); + } } /// @@ -947,7 +951,7 @@ namespace Umbraco.Tests.Services // Act contentService.RePublishAll(new int[] { allContent.Last().ContentTypeId }); - // Assert + // Assert using (var uow = provider.GetUnitOfWork()) { Assert.AreEqual(allContent.Count(), uow.Database.ExecuteScalar("select count(*) from cmsContentXml")); diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 6bd44c2bdd..dc3a0e83b6 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -72,6 +72,12 @@ namespace Umbraco.Tests.TestHelpers GetDbProviderName(), Logger); + // fixme - bah + var scopeProvider = new ScopeProvider(null); + if (scopeProvider.AmbientScope != null) + scopeProvider.AmbientScope.Dispose(); + scopeProvider.AmbientScope = null; + base.Initialize(); using (ProfilingLogger.TraceDuration("init")) diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 1f5e324142..cd7010310c 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -168,6 +168,7 @@ + diff --git a/src/umbraco.cms/businesslogic/Content.cs b/src/umbraco.cms/businesslogic/Content.cs index 4b942d453f..b64e4cf678 100644 --- a/src/umbraco.cms/businesslogic/Content.cs +++ b/src/umbraco.cms/businesslogic/Content.cs @@ -149,7 +149,7 @@ namespace umbraco.cms.businesslogic { _contentType = new ContentType(contentTypeId); } - catch + catch (Exception e) { return null; } diff --git a/src/umbraco.datalayer/SqlHelper.cs b/src/umbraco.datalayer/SqlHelper.cs index 4420e8bde0..10d1183abb 100644 --- a/src/umbraco.datalayer/SqlHelper.cs +++ b/src/umbraco.datalayer/SqlHelper.cs @@ -359,11 +359,12 @@ namespace umbraco.DataLayer internal class CurrentConnectionUsing : IDisposable { - public static MethodInfo OpenMethod { get; private set; } - public static MethodInfo CloseMethod { get; private set; } + private static MethodInfo OpenMethod { get; set; } + //private static MethodInfo CloseMethod { get; set; } - private static readonly object Factory; - private static readonly MethodInfo CreateMethod; + private static readonly object ScopeProvider; + private static readonly PropertyInfo ScopeProviderAmbientOrNoScopeProperty; + private static readonly PropertyInfo ScopeDatabaseProperty; private static readonly PropertyInfo ConnectionProperty; private static readonly FieldInfo TransactionField; private static readonly PropertyInfo InnerConnectionProperty; @@ -378,23 +379,30 @@ namespace umbraco.DataLayer var applicationContextType = coreAssembly.GetType("Umbraco.Core.ApplicationContext"); var databaseContextType = coreAssembly.GetType("Umbraco.Core.DatabaseContext"); - var defaultDatabaseFactoryType = coreAssembly.GetType("Umbraco.Core.Persistence.DefaultDatabaseFactory"); var umbracoDatabaseType = coreAssembly.GetType("Umbraco.Core.Persistence.UmbracoDatabase"); var databaseType = coreAssembly.GetType("Umbraco.Core.Persistence.Database"); + var scopeProviderType = coreAssembly.GetType("Umbraco.Core.Scoping.IScopeProviderInternal"); + var scopeType = coreAssembly.GetType("Umbraco.Core.Scoping.IScope"); - var currentProperty = applicationContextType.GetProperty("Current", BindingFlags.Static | BindingFlags.Public); - var applicationContext = currentProperty.GetValue(null, NoArgs); + var applicationContextCurrentProperty = applicationContextType.GetProperty("Current", BindingFlags.Static | BindingFlags.Public); + if (applicationContextCurrentProperty == null) throw new Exception("oops: applicationContextCurrentProperty."); + var applicationContext = applicationContextCurrentProperty.GetValue(null, NoArgs); - var databaseContextProperty = applicationContextType.GetProperty("DatabaseContext", BindingFlags.Instance | BindingFlags.Public); - var databaseContext = databaseContextProperty.GetValue(applicationContext, NoArgs); + var applicationContextDatabaseContextProperty = applicationContextType.GetProperty("DatabaseContext", BindingFlags.Instance | BindingFlags.Public); + if (applicationContextDatabaseContextProperty == null) throw new Exception("oops: applicationContextDatabaseContextProperty."); + var databaseContext = applicationContextDatabaseContextProperty.GetValue(applicationContext, NoArgs); - var factoryField = databaseContextType.GetField("_factory", BindingFlags.Instance | BindingFlags.NonPublic); - Factory = factoryField.GetValue(databaseContext); + var databaseContextScopeProviderField = databaseContextType.GetField("ScopeProvider", BindingFlags.Instance | BindingFlags.NonPublic); + if (databaseContextScopeProviderField == null) throw new Exception("oops: databaseContextScopeProviderField."); + ScopeProvider = databaseContextScopeProviderField.GetValue(databaseContext); - CreateMethod = defaultDatabaseFactoryType.GetMethod("CreateDatabase", BindingFlags.Instance | BindingFlags.Public); + ScopeProviderAmbientOrNoScopeProperty = scopeProviderType.GetProperty("AmbientOrNoScope", BindingFlags.Instance | BindingFlags.Public); + if (ScopeProviderAmbientOrNoScopeProperty == null) throw new Exception("oops: ScopeProviderAmbientOrNoScopeProperty."); + ScopeDatabaseProperty = scopeType.GetProperty("Database", BindingFlags.Instance | BindingFlags.Public); + if (ScopeDatabaseProperty == null) throw new Exception("oops: ScopeDatabaseProperty."); OpenMethod = databaseType.GetMethod("OpenSharedConnection", BindingFlags.Instance | BindingFlags.Public); - CloseMethod = databaseType.GetMethod("CloseSharedConnection", BindingFlags.Instance | BindingFlags.Public); + //CloseMethod = databaseType.GetMethod("CloseSharedConnection", BindingFlags.Instance | BindingFlags.Public); ConnectionProperty = umbracoDatabaseType.GetProperty("Connection", BindingFlags.Instance | BindingFlags.Public); TransactionField = databaseType.GetField("_transaction", BindingFlags.Instance | BindingFlags.NonPublic); @@ -408,7 +416,8 @@ namespace umbraco.DataLayer public CurrentConnectionUsing() { - _database = CreateMethod.Invoke(Factory, NoArgs); + var scope = ScopeProviderAmbientOrNoScopeProperty.GetValue(ScopeProvider); + _database = ScopeDatabaseProperty.GetValue(scope); var connection = ConnectionProperty.GetValue(_database, NoArgs); // we have to open to make sure that we *do* have a connection diff --git a/src/umbraco.datalayer/SqlHelpers/MySql/MySqlHelper.cs b/src/umbraco.datalayer/SqlHelpers/MySql/MySqlHelper.cs index 2e76941c21..c1a530f8f4 100644 --- a/src/umbraco.datalayer/SqlHelpers/MySql/MySqlHelper.cs +++ b/src/umbraco.datalayer/SqlHelpers/MySql/MySqlHelper.cs @@ -122,12 +122,12 @@ namespace umbraco.DataLayer.SqlHelpers.MySql { using (var cc = UseCurrentConnection) { - return new MySqlDataReader(ExecuteReader((MSC.MySqlConnection) cc.Connection, (MSC.MySqlTransaction) cc.Transaction, commandText, parameters, true)); + return new MySqlDataReader(ExecuteReader((MSC.MySqlConnection) cc.Connection, (MSC.MySqlTransaction) cc.Transaction, commandText, parameters)); } } // copied & adapted from MySqlHelper - private static MSC.MySqlDataReader ExecuteReader(MSC.MySqlConnection connection, MSC.MySqlTransaction trx, string commandText, MSC.MySqlParameter[] commandParameters, bool externalConn) + private static MSC.MySqlDataReader ExecuteReader(MSC.MySqlConnection connection, MSC.MySqlTransaction trx, string commandText, MSC.MySqlParameter[] commandParameters) { MSC.MySqlCommand mySqlCommand = new MSC.MySqlCommand(); mySqlCommand.Connection = connection; @@ -139,7 +139,7 @@ namespace umbraco.DataLayer.SqlHelpers.MySql foreach (var commandParameter in commandParameters) mySqlCommand.Parameters.Add(commandParameter); } - MSC.MySqlDataReader mySqlDataReader = !externalConn ? mySqlCommand.ExecuteReader(CommandBehavior.CloseConnection) : mySqlCommand.ExecuteReader(); + MSC.MySqlDataReader mySqlDataReader = mySqlCommand.ExecuteReader(); mySqlCommand.Parameters.Clear(); return mySqlDataReader; }