From 291b4364c3b9e8e18f547719d024998b77da69f5 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 11 Jul 2017 19:26:55 +0200 Subject: [PATCH] Scope/UnitOfWork db context and locking --- .../Persistence/UnitOfWork/ScopeUnitOfWork.cs | 26 ++------------ src/Umbraco.Core/Scoping/IScope.cs | 17 +++++++++ src/Umbraco.Core/Scoping/IScopeProvider.cs | 7 ++++ src/Umbraco.Core/Scoping/Scope.cs | 36 +++++++++++++++++++ src/Umbraco.Core/Scoping/ScopeProvider.cs | 2 ++ 5 files changed, 64 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWork.cs index 302eb75b99..0227467cb0 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/ScopeUnitOfWork.cs @@ -86,18 +86,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork /// public void ReadLock(params int[] lockIds) { - // soon as we get Database, a transaction is started - - if (Database.Transaction.IsolationLevel < IsolationLevel.RepeatableRead) - throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required."); - - // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks - foreach (var lockId in lockIds) - { - var i = Database.ExecuteScalar("SELECT value FROM umbracoLock WHERE id=@id", new { id = lockId }); - if (i == null) // ensure we are actually locking! - throw new Exception($"LockObject with id={lockId} does not exist."); - } + Scope.ReadLock(lockIds); } /// @@ -106,18 +95,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork if (ReadOnly) throw new NotSupportedException("This unit of work is read-only."); - // soon as we get Database, a transaction is started - - if (Database.Transaction.IsolationLevel < IsolationLevel.RepeatableRead) - throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required."); - - // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks - foreach (var lockId in lockIds) - { - var i = Database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId }); - if (i == 0) // ensure we are actually locking! - throw new Exception($"LockObject with id={lockId} does not exist."); - } + Scope.WriteLock(lockIds); } public override void Complete() diff --git a/src/Umbraco.Core/Scoping/IScope.cs b/src/Umbraco.Core/Scoping/IScope.cs index 1c7d0dae43..82cdc71b4f 100644 --- a/src/Umbraco.Core/Scoping/IScope.cs +++ b/src/Umbraco.Core/Scoping/IScope.cs @@ -15,6 +15,11 @@ namespace Umbraco.Core.Scoping /// IUmbracoDatabase Database { get; } + /// + /// Gets the database context. + /// + IDatabaseContext DatabaseContext { get; } + /// /// Gets the scope event messages. /// @@ -41,5 +46,17 @@ namespace Umbraco.Core.Scoping /// A value indicating whether the scope has been successfully completed. /// Can return false if any child scope has not completed. bool Complete(); + + /// + /// Read-locks some lock objects. + /// + /// The lock object identifiers. + void ReadLock(params int[] lockIds); + + /// + /// Write-locks some lock objects. + /// + /// The lock object identifiers. + void WriteLock(params int[] lockIds); } } diff --git a/src/Umbraco.Core/Scoping/IScopeProvider.cs b/src/Umbraco.Core/Scoping/IScopeProvider.cs index fa8cbf72e5..cc35790588 100644 --- a/src/Umbraco.Core/Scoping/IScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/IScopeProvider.cs @@ -1,6 +1,8 @@ using System; using System.Data; using Umbraco.Core.Events; +using Umbraco.Core.Persistence; + #if DEBUG_SCOPES using System.Collections.Generic; #endif @@ -76,6 +78,11 @@ namespace Umbraco.Core.Scoping /// IScopeContext Context { get; } + /// + /// Gets the database context. + /// + IDatabaseContext DatabaseContext { get; } + #if DEBUG_SCOPES Dictionary CallContextObjects { get; } IEnumerable ScopeInfos { get; } diff --git a/src/Umbraco.Core/Scoping/Scope.cs b/src/Umbraco.Core/Scoping/Scope.cs index cec84f0368..ad43a44bb9 100644 --- a/src/Umbraco.Core/Scoping/Scope.cs +++ b/src/Umbraco.Core/Scoping/Scope.cs @@ -136,6 +136,8 @@ namespace Umbraco.Core.Scoping public Guid InstanceId { get; } = Guid.NewGuid(); + public IDatabaseContext DatabaseContext => _scopeProvider.DatabaseContext; + // a value indicating whether to force call-context public bool CallContext { @@ -481,5 +483,39 @@ namespace Umbraco.Core.Scoping // true if Umbraco.CoreDebug.LogUncompletedScope appSetting is set to "true" private static bool LogUncompletedScopes => (_logUncompletedScopes ?? (_logUncompletedScopes = UmbracoConfig.For.CoreDebug().LogUncompletedScopes)).Value; + + /// + public void ReadLock(params int[] lockIds) + { + // soon as we get Database, a transaction is started + + if (Database.Transaction.IsolationLevel < IsolationLevel.RepeatableRead) + throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required."); + + // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks + foreach (var lockId in lockIds) + { + var i = Database.ExecuteScalar("SELECT value FROM umbracoLock WHERE id=@id", new { id = lockId }); + if (i == null) // ensure we are actually locking! + throw new Exception($"LockObject with id={lockId} does not exist."); + } + } + + /// + public void WriteLock(params int[] lockIds) + { + // soon as we get Database, a transaction is started + + if (Database.Transaction.IsolationLevel < IsolationLevel.RepeatableRead) + throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required."); + + // *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks + foreach (var lockId in lockIds) + { + var i = Database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId }); + if (i == 0) // ensure we are actually locking! + throw new Exception($"LockObject with id={lockId} does not exist."); + } + } } } diff --git a/src/Umbraco.Core/Scoping/ScopeProvider.cs b/src/Umbraco.Core/Scoping/ScopeProvider.cs index 97e5ea5cc3..6707281fb6 100644 --- a/src/Umbraco.Core/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/ScopeProvider.cs @@ -63,6 +63,8 @@ namespace Umbraco.Core.Scoping public IUmbracoDatabaseFactory DatabaseFactory { get; } + public IDatabaseContext DatabaseContext => DatabaseFactory; + #region Context // objects that go into the logical call context better be serializable else they'll eventually