Scope/UnitOfWork db context and locking

This commit is contained in:
Stephan
2017-07-11 19:26:55 +02:00
parent 5897881ca8
commit 291b4364c3
5 changed files with 64 additions and 24 deletions

View File

@@ -86,18 +86,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork
/// <inheritdoc />
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<int?>("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);
}
/// <inheritdoc />
@@ -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()

View File

@@ -15,6 +15,11 @@ namespace Umbraco.Core.Scoping
/// </summary>
IUmbracoDatabase Database { get; }
/// <summary>
/// Gets the database context.
/// </summary>
IDatabaseContext DatabaseContext { get; }
/// <summary>
/// Gets the scope event messages.
/// </summary>
@@ -41,5 +46,17 @@ namespace Umbraco.Core.Scoping
/// <returns>A value indicating whether the scope has been successfully completed.</returns>
/// <remarks>Can return false if any child scope has not completed.</remarks>
bool Complete();
/// <summary>
/// Read-locks some lock objects.
/// </summary>
/// <param name="lockIds">The lock object identifiers.</param>
void ReadLock(params int[] lockIds);
/// <summary>
/// Write-locks some lock objects.
/// </summary>
/// <param name="lockIds">The lock object identifiers.</param>
void WriteLock(params int[] lockIds);
}
}

View File

@@ -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
/// </summary>
IScopeContext Context { get; }
/// <summary>
/// Gets the database context.
/// </summary>
IDatabaseContext DatabaseContext { get; }
#if DEBUG_SCOPES
Dictionary<Guid, object> CallContextObjects { get; }
IEnumerable<ScopeInfo> ScopeInfos { get; }

View File

@@ -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;
/// <inheritdoc />
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<int?>("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.");
}
}
/// <inheritdoc />
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.");
}
}
}
}

View File

@@ -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