Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/Umbraco.Cms.Persistence.EFCore/Scoping/EFCoreScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,16 @@ public override void Dispose()

Locks.ClearLocks(InstanceId);

if (ParentScope is null)
// Since we can nest EFCoreScopes in other scopes derived from CoreScope, we should check whether our ParentScope OR the base ParentScope exists.
// Only if neither do do we take responsibility for ensuring the locks are cleared.
// Eventually the highest parent will clear the locks.
// Further, these locks are a reference to the locks of the highest parent anyway (see the constructor of CoreScope).
#pragma warning disable SA1100 // Do not prefix calls with base unless local implementation exists (justification: provides additional clarify here that this is defined on the base class).
if (ParentScope is null && base.HasParentScope is false)
{
Locks.EnsureLocksCleared(InstanceId);
}
#pragma warning restore SA1100 // Do not prefix calls with base unless local implementation exists

_efCoreScopeProvider.PopAmbientScope();

Expand Down
2 changes: 2 additions & 0 deletions src/Umbraco.Core/Scoping/CoreScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ protected void SetParentScope(ICoreScope coreScope)
_parentScope = coreScope;
}

protected bool HasParentScope => _parentScope is not null;

protected void HandleScopedNotifications() => _notificationPublisher?.ScopeExit(Completed.HasValue && Completed.Value);

private void EnsureNotDisposed()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Cms.Core;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Persistence.EFCore.Scoping;
using Umbraco.Cms.Tests.Common.Testing;
using Umbraco.Cms.Tests.Integration.Testing;
using Umbraco.Cms.Tests.Integration.Umbraco.Persistence.EFCore.DbContext;
using IScopeProvider = Umbraco.Cms.Infrastructure.Scoping.IScopeProvider;

namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Scoping
{
/// <summary>
/// These tests verify that the various types of scopes we have can be created and disposed within each other.
/// </summary>
/// <remarks>
/// Scopes are:
/// - "Normal" - created by <see cref="IScopeProvider"/>"/>.
/// - "Core" - created by <see cref="ICoreScopeProvider"/>"/>.
/// - "EFCore" - created by <see cref="IEFCoreScopeProvider{TDbContext}"/>"/>.
/// </remarks>
[TestFixture]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
internal sealed class NestedScopeTests : UmbracoIntegrationTest
{
private new IScopeProvider ScopeProvider => Services.GetRequiredService<IScopeProvider>();

private ICoreScopeProvider CoreScopeProvider => Services.GetRequiredService<ICoreScopeProvider>();

private IEFCoreScopeProvider<TestUmbracoDbContext> EfCoreScopeProvider =>
Services.GetRequiredService<IEFCoreScopeProvider<TestUmbracoDbContext>>();

[Test]
public void CanNestScopes_Normal_Core_EfCore()
{
using (var ambientScope = ScopeProvider.CreateScope())
{
ambientScope.WriteLock(Constants.Locks.ContentTree);

using (var outerScope = CoreScopeProvider.CreateCoreScope())
{
outerScope.WriteLock(Constants.Locks.ContentTree);

using (var innerScope = EfCoreScopeProvider.CreateScope())
{
innerScope.WriteLock(Constants.Locks.ContentTree);

innerScope.Complete();
outerScope.Complete();
ambientScope.Complete();
}
}
}
}

Check warning on line 54 in tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/NestedScopeTests.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Code Duplication

The module contains 9 functions with similar structure: CanNestScopes_Core_Core,CanNestScopes_Core_EfCore_Normal,CanNestScopes_Core_Normal_Efcore,CanNestScopes_EfCore_Core_Normal and 5 more functions. Avoid duplicated, aka copy-pasted, code inside the module. More duplication lowers the code health.

[Test]
public void CanNestScopes_Normal_EfCore_Core()
{
using (var ambientScope = ScopeProvider.CreateScope())
{
ambientScope.WriteLock(Constants.Locks.ContentTree);

using (var outerScope = EfCoreScopeProvider.CreateScope())
{
outerScope.WriteLock(Constants.Locks.ContentTree);

using (var innerScope = CoreScopeProvider.CreateCoreScope())
{
innerScope.WriteLock(Constants.Locks.ContentTree);

innerScope.Complete();
outerScope.Complete();
ambientScope.Complete();
}
}
}
}

[Test]
public void CanNestScopes_Core_Normal_Efcore()
{
using (var ambientScope = CoreScopeProvider.CreateCoreScope())
{
ambientScope.WriteLock(Constants.Locks.ContentTree);

using (var outerScope = ScopeProvider.CreateScope())
{
outerScope.WriteLock(Constants.Locks.ContentTree);

using (var innerScope = EfCoreScopeProvider.CreateScope())
{
innerScope.WriteLock(Constants.Locks.ContentTree);

innerScope.Complete();
outerScope.Complete();
ambientScope.Complete();
}
}
}
}

[Test]
public void CanNestScopes_Core_EfCore_Normal()
{
using (var ambientScope = CoreScopeProvider.CreateCoreScope())
{
ambientScope.WriteLock(Constants.Locks.ContentTree);

using (var outerScope = EfCoreScopeProvider.CreateScope())
{
outerScope.WriteLock(Constants.Locks.ContentTree);

using (var innerScope = ScopeProvider.CreateScope())
{
innerScope.WriteLock(Constants.Locks.ContentTree);

innerScope.Complete();
outerScope.Complete();
ambientScope.Complete();
}
}
}
}

[Test]
public void CanNestScopes_EfCore_Normal_Core()
{
using (var ambientScope = EfCoreScopeProvider.CreateScope())
{
ambientScope.WriteLock(Constants.Locks.ContentTree);

using (var outerScope = ScopeProvider.CreateScope())
{
outerScope.WriteLock(Constants.Locks.ContentTree);

using (var innerScope = CoreScopeProvider.CreateCoreScope())
{
innerScope.WriteLock(Constants.Locks.ContentTree);

innerScope.Complete();
outerScope.Complete();
ambientScope.Complete();
}
}
}
}

[Test]
public void CanNestScopes_EfCore_Core_Normal()
{
using (var ambientScope = EfCoreScopeProvider.CreateScope())
{
ambientScope.WriteLock(Constants.Locks.ContentTree);

using (var outerScope = CoreScopeProvider.CreateCoreScope())
{
outerScope.WriteLock(Constants.Locks.ContentTree);

using (var innerScope = ScopeProvider.CreateScope())
{
innerScope.WriteLock(Constants.Locks.ContentTree);

innerScope.Complete();
outerScope.Complete();
ambientScope.Complete();
}
}
}
}

[Test]
public void CanNestScopes_Normal_Normal()
{
using (var ambientScope = ScopeProvider.CreateScope())
{
ambientScope.WriteLock(Constants.Locks.ContentTree);

using (var inner = ScopeProvider.CreateScope())
{
inner.WriteLock(Constants.Locks.ContentTree);

inner.Complete();
ambientScope.Complete();
}
}
}

[Test]
public void CanNestScopes_Core_Core()
{
using (var ambientScope = CoreScopeProvider.CreateCoreScope())
{
ambientScope.WriteLock(Constants.Locks.ContentTree);

using (var inner = CoreScopeProvider.CreateCoreScope())
{
inner.WriteLock(Constants.Locks.ContentTree);

inner.Complete();
ambientScope.Complete();
}
}
}

[Test]
public void CanNestScopes_EfCore_EfCore()
{
using (var ambientScope = EfCoreScopeProvider.CreateScope())
{
ambientScope.WriteLock(Constants.Locks.ContentTree);

using (var inner = EfCoreScopeProvider.CreateScope())
{
inner.WriteLock(Constants.Locks.ContentTree);

inner.Complete();
ambientScope.Complete();
}
}
}
}
}