From 0d36ca0534ce47fd196de7b4ef2e9eae1faf3b85 Mon Sep 17 00:00:00 2001 From: mole Date: Tue, 8 Jul 2025 11:02:31 +0200 Subject: [PATCH 01/14] Add RepositoryCacheVersion table --- .../Persistence/Constants-DatabaseSchema.cs | 2 ++ .../Migrations/Upgrade/UmbracoPlan.cs | 3 +++ .../AddRepositoryCacheVersionTable.cs | 21 +++++++++++++++++ .../Dtos/RepositoryCacheVersionDto.cs | 23 +++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_16_2_0/AddRepositoryCacheVersionTable.cs create mode 100644 src/Umbraco.Infrastructure/Persistence/Dtos/RepositoryCacheVersionDto.cs diff --git a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs index c275fdd10890..695453f4a717 100644 --- a/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs +++ b/src/Umbraco.Core/Persistence/Constants-DatabaseSchema.cs @@ -98,6 +98,8 @@ public static class Tables public const string Webhook2Headers = Webhook + "2Headers"; public const string WebhookLog = Webhook + "Log"; public const string WebhookRequest = Webhook + "Request"; + + public const string RepositoryCacheVersion = TableNamePrefix + "RepositoryCacheVersion"; } } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index 21cfe558a688..fd49eeda84dc 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -119,5 +119,8 @@ protected virtual void DefinePlan() // To 16.0.0 To("{C6681435-584F-4BC8-BB8D-BC853966AF0B}"); To("{D1568C33-A697-455F-8D16-48060CB954A1}"); + + // TO 16.2.0 + To("{A1B3F5D6-4C8B-4E7A-9F8C-1D2B3E4F5A6B}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_16_2_0/AddRepositoryCacheVersionTable.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_16_2_0/AddRepositoryCacheVersionTable.cs new file mode 100644 index 000000000000..e80cc2e21c91 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_16_2_0/AddRepositoryCacheVersionTable.cs @@ -0,0 +1,21 @@ +using Umbraco.Cms.Infrastructure.Migrations.Expressions.Create.Table; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_16_2_0; + +public class AddRepositoryCacheVersionTable : AsyncMigrationBase +{ + public AddRepositoryCacheVersionTable(IMigrationContext context) : base(context) + { + } + + protected override Task MigrateAsync() + { + if (TableExists(RepositoryCacheVersionDto.TableName) is false) + { + Create.Table().Do(); + } + + return Task.CompletedTask; + } +} diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/RepositoryCacheVersionDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/RepositoryCacheVersionDto.cs new file mode 100644 index 000000000000..04d1161876ad --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/RepositoryCacheVersionDto.cs @@ -0,0 +1,23 @@ +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; + +namespace Umbraco.Cms.Infrastructure.Persistence.Dtos; + +[TableName(TableName)] +[PrimaryKey("cacheIdentifier")] +[ExplicitColumns] +public class RepositoryCacheVersionDto +{ + internal const string TableName = Constants.DatabaseSchema.Tables.RepositoryCacheVersion; + + [Column("cacheIdentifier")] + [Length(256)] + [PrimaryKeyColumn(AutoIncrement = false, Clustered = true)] + public required string CacheIdentifier { get; set; } + + [Column("version")] + [Length(256)] + [NullSetting(NullSetting = NullSettings.Null)] + public string? Version { get; set; } +} From 6e1e6916ab4d5abb49764c212b58a20d832dcd10 Mon Sep 17 00:00:00 2001 From: mole Date: Tue, 8 Jul 2025 11:23:34 +0200 Subject: [PATCH 02/14] Add repository --- .../Models/RepositoryCacheVersion.cs | 17 ++++++ .../IRepositoryCacheVersionRepository.cs | 24 +++++++++ .../UmbracoBuilder.Repositories.cs | 1 + .../Dtos/RepositoryCacheVersionDto.cs | 4 +- .../RepositoryCacheVersionRepository.cs | 52 +++++++++++++++++++ 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Core/Models/RepositoryCacheVersion.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/IRepositoryCacheVersionRepository.cs create mode 100644 src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryCacheVersionRepository.cs diff --git a/src/Umbraco.Core/Models/RepositoryCacheVersion.cs b/src/Umbraco.Core/Models/RepositoryCacheVersion.cs new file mode 100644 index 000000000000..d9dd50b35a0d --- /dev/null +++ b/src/Umbraco.Core/Models/RepositoryCacheVersion.cs @@ -0,0 +1,17 @@ +namespace Umbraco.Cms.Core.Models; + +/// +/// Represents a version of a repository cache. +/// +public class RepositoryCacheVersion +{ + /// + /// The unique identifier for the cache. + /// + public required string Identifier { get; init; } + + /// + /// The identifier of the version of the cache. + /// + public required string? Version { get; init; } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/IRepositoryCacheVersionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRepositoryCacheVersionRepository.cs new file mode 100644 index 000000000000..56a71178316d --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/IRepositoryCacheVersionRepository.cs @@ -0,0 +1,24 @@ +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Cms.Core.Persistence.Repositories; + +/// +/// Defines methods for accessing and persisting entities. +/// +public interface IRepositoryCacheVersionRepository : IRepository +{ + /// + /// Gets a by its identifier. + /// + /// The unique identifier of the cache version. + /// + /// A if found; otherwise, null. + /// + Task GetAsync(string identifier); + + /// + /// Saves the specified . + /// + /// The cache version entity to save. + Task SaveAsync(RepositoryCacheVersion repositoryCacheVersion); +} diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs index 73ee0d263a3d..0a4021dbb8c7 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Repositories.cs @@ -82,6 +82,7 @@ internal static IUmbracoBuilder AddRepositories(this IUmbracoBuilder builder) builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); + builder.Services.AddUnique(); return builder; } diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/RepositoryCacheVersionDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/RepositoryCacheVersionDto.cs index 04d1161876ad..dc227df2a4f3 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/RepositoryCacheVersionDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/RepositoryCacheVersionDto.cs @@ -11,10 +11,10 @@ public class RepositoryCacheVersionDto { internal const string TableName = Constants.DatabaseSchema.Tables.RepositoryCacheVersion; - [Column("cacheIdentifier")] + [Column("identifier")] [Length(256)] [PrimaryKeyColumn(AutoIncrement = false, Clustered = true)] - public required string CacheIdentifier { get; set; } + public required string Identifier { get; set; } [Column("version")] [Length(256)] diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryCacheVersionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryCacheVersionRepository.cs new file mode 100644 index 000000000000..decdbd2ee34f --- /dev/null +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryCacheVersionRepository.cs @@ -0,0 +1,52 @@ +using NPoco; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Cms.Infrastructure.Scoping; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; + +/// +internal class RepositoryCacheVersionRepository : RepositoryBase, IRepositoryCacheVersionRepository +{ + public RepositoryCacheVersionRepository(IScopeAccessor scopeAccessor, AppCaches appCaches) + : base(scopeAccessor, appCaches) + { + } + + /// + public async Task GetAsync(string identifier) + { + if (string.IsNullOrWhiteSpace(identifier)) + { + throw new ArgumentException("Identifier cannot be null or whitespace.", nameof(identifier)); + } + + Sql query = Sql() + .Select() + .From() + .Where(x => x.Identifier == identifier); + + RepositoryCacheVersionDto? dto = (await Database.FetchAsync(query)).FirstOrDefault(); + + return Map(dto); + } + + /// + public async Task SaveAsync(RepositoryCacheVersion repositoryCacheVersion) + { + RepositoryCacheVersionDto dto = Map(repositoryCacheVersion); + await Database.InsertOrUpdateAsync( + dto, + "SET identifier = @identifier, version = @version", + new { identifier = dto.Identifier, version = dto.Version, }); + } + + private static RepositoryCacheVersionDto Map(RepositoryCacheVersion entity) + => new() { Identifier = entity.Identifier, Version = entity.Version }; + + private static RepositoryCacheVersion? Map(RepositoryCacheVersionDto? dto) + => dto is null ? null : new RepositoryCacheVersion { Identifier = dto.Identifier, Version = dto.Version }; +} From 71a3506f8d9062bd251c232ca9cb7ffb750a904c Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Wed, 9 Jul 2025 10:44:58 +0200 Subject: [PATCH 03/14] Add Cache version lock --- .../Persistence/Constants-Locks.cs | 5 +++ .../V_16_2_0/AddCacheVersionDatabaseLock.cs | 33 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 src/Umbraco.Infrastructure/Migrations/Upgrade/V_16_2_0/AddCacheVersionDatabaseLock.cs diff --git a/src/Umbraco.Core/Persistence/Constants-Locks.cs b/src/Umbraco.Core/Persistence/Constants-Locks.cs index 874c0ffe2fc3..94558882649e 100644 --- a/src/Umbraco.Core/Persistence/Constants-Locks.cs +++ b/src/Umbraco.Core/Persistence/Constants-Locks.cs @@ -80,5 +80,10 @@ public static class Locks /// All webhook logs. /// public const int WebhookLogs = -343; + + /// + /// The cache version. + /// + public const int CacheVersion = -344; } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_16_2_0/AddCacheVersionDatabaseLock.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_16_2_0/AddCacheVersionDatabaseLock.cs new file mode 100644 index 000000000000..5a779b8fdb78 --- /dev/null +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_16_2_0/AddCacheVersionDatabaseLock.cs @@ -0,0 +1,33 @@ +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_16_2_0; + +public class AddCacheVersionDatabaseLock : AsyncMigrationBase +{ + public AddCacheVersionDatabaseLock(IMigrationContext context) + : base(context) + { + } + + protected override Task MigrateAsync() + { + Sql sql = Database.SqlContext.Sql() + .Select() + .From() + .Where(x => x.Id == Constants.Locks.CacheVersion); + + LockDto? cacheVersionLock = Database.Fetch(sql).FirstOrDefault(); + + + if (cacheVersionLock is null) + { + Database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.CacheVersion, Name = "CacheVersion" }); + } + + return Task.CompletedTask; + } +} From 7e277d9d2d77c10b8e2b291894110412326d03dd Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Wed, 9 Jul 2025 10:56:17 +0200 Subject: [PATCH 04/14] Add GetAll method to repository --- .../Repositories/IRepositoryCacheVersionRepository.cs | 8 ++++++++ .../Implement/RepositoryCacheVersionRepository.cs | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/IRepositoryCacheVersionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IRepositoryCacheVersionRepository.cs index 56a71178316d..6fac4c834da5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IRepositoryCacheVersionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IRepositoryCacheVersionRepository.cs @@ -16,6 +16,14 @@ public interface IRepositoryCacheVersionRepository : IRepository /// Task GetAsync(string identifier); + /// + /// Gets all entities. + /// + /// + /// An containing all cache versions. + /// + Task> GetAllAsync(); + /// /// Saves the specified . /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryCacheVersionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryCacheVersionRepository.cs index decdbd2ee34f..d64f31afdc31 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryCacheVersionRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryCacheVersionRepository.cs @@ -34,6 +34,16 @@ public RepositoryCacheVersionRepository(IScopeAccessor scopeAccessor, AppCaches return Map(dto); } + public async Task> GetAllAsync() + { + Sql query = Sql() + .Select() + .From(); + + IEnumerable dtos = await Database.FetchAsync(query); + return dtos.Select(Map).Where(x => x is not null)!; + } + /// public async Task SaveAsync(RepositoryCacheVersion repositoryCacheVersion) { From 1c4a8ae53310c8a23780094a17218b4bf7281b11 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Wed, 9 Jul 2025 10:56:52 +0200 Subject: [PATCH 05/14] Add RepositoryCacheVersionService --- .../Cache/IRepositoryCacheVersionService.cs | 28 +++++ .../Cache/RepositoryCacheVersionService.cs | 117 ++++++++++++++++++ .../Cache/SingleServerCacheVersionService.cs | 23 ++++ .../DependencyInjection/UmbracoBuilder.cs | 1 + 4 files changed, 169 insertions(+) create mode 100644 src/Umbraco.Core/Cache/IRepositoryCacheVersionService.cs create mode 100644 src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs create mode 100644 src/Umbraco.Core/Cache/SingleServerCacheVersionService.cs diff --git a/src/Umbraco.Core/Cache/IRepositoryCacheVersionService.cs b/src/Umbraco.Core/Cache/IRepositoryCacheVersionService.cs new file mode 100644 index 000000000000..ba6c31a0b003 --- /dev/null +++ b/src/Umbraco.Core/Cache/IRepositoryCacheVersionService.cs @@ -0,0 +1,28 @@ +namespace Umbraco.Cms.Core.Cache; + +/// +/// Provides methods to manage and validate cache versioning for repository entities, +/// ensuring cache consistency with the underlying database. +/// +public interface IRepositoryCacheVersionService +{ + /// + /// Validates if the cache is synced with the database. + /// + /// The type of the cached entity. + /// True if cache is synced, false if cache needs fast-forwarding. + Task IsCacheSyncedAsync() + where TEntity : class; + + /// + /// Registers a cache update for the specified entity type. + /// + /// The type of the cached entity. + Task SetCacheUpdatedAsync() + where TEntity : class; + + /// + /// Registers that the cache has been synced with the database + /// + Task SetCachesSyncedAsync(); +} diff --git a/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs b/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs new file mode 100644 index 000000000000..e2809963c50e --- /dev/null +++ b/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs @@ -0,0 +1,117 @@ +using System.Collections.Concurrent; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; + +namespace Umbraco.Cms.Core.Cache; + +/// +public class RepositoryCacheVersionService : IRepositoryCacheVersionService +{ + private readonly ICoreScopeProvider _scopeProvider; + private readonly IRepositoryCacheVersionRepository _repositoryCacheVersionRepository; + private readonly ILogger _logger; + private readonly ConcurrentDictionary _cacheVersions = new(); + + public RepositoryCacheVersionService( + ICoreScopeProvider scopeProvider, + IRepositoryCacheVersionRepository repositoryCacheVersionRepository, + ILogger logger) + { + _scopeProvider = scopeProvider; + _repositoryCacheVersionRepository = repositoryCacheVersionRepository; + _logger = logger; + } + + /// + public async Task IsCacheSyncedAsync() + where TEntity : class + { + _logger.LogDebug("Checking if cache for {EntityType} is synced", typeof(TEntity).Name); + + // We have to take a read lock to ensure the cache is not being updated while we check the version + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + scope.ReadLock(Constants.Locks.CacheVersion); + + var cacheKey = GetCacheKey(); + if (_cacheVersions.TryGetValue(cacheKey, out Guid localVersion) is false) + { + _logger.LogDebug("Cache for {EntityType} is not initialized, considering it synced", typeof(TEntity).Name); + + // We're not initialized yet, so cache is empty, which means cache is synced. + return true; + } + + RepositoryCacheVersion? databaseVersion = await _repositoryCacheVersionRepository.GetAsync(cacheKey); + scope.Complete(); + + if (databaseVersion?.Version is null) + { + _logger.LogDebug("Cache for {EntityType} has no version in the database, considering it synced", typeof(TEntity).Name); + + // If the database version is null, it means the cache has never been initialized, so we consider it synced. + return true; + } + + // We could've parsed this in the repository layer; however, the fact that we are using a Guid is an implementation detail. + if (localVersion != Guid.Parse(databaseVersion.Version)) + { + _logger.LogDebug( + "Cache for {EntityType} is not synced: local version {LocalVersion} does not match database version {DatabaseVersion}", + typeof(TEntity).Name, + localVersion, + databaseVersion.Version); + return false; + } + + _logger.LogDebug("Cache for {EntityType} is synced", typeof(TEntity).Name); + return true; + + } + + /// + public async Task SetCacheUpdatedAsync() + where TEntity : class + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + + // We have to take a write lock to ensure the cache is not being read while we update the version + scope.WriteLock(Constants.Locks.CacheVersion); + + var cacheKey = GetCacheKey(); + var newVersion = Guid.NewGuid(); + + _logger.LogDebug("Setting cache for {EntityType} to version {Version}", typeof(TEntity).Name, newVersion); + await _repositoryCacheVersionRepository.SaveAsync(new RepositoryCacheVersion { Identifier = cacheKey, Version = newVersion.ToString() }); + _cacheVersions[cacheKey] = newVersion; + + scope.Complete(); + } + + /// + public async Task SetCachesSyncedAsync() + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + scope.ReadLock(Constants.Locks.CacheVersion); + + // We always sync all caches versions, so it's safe to assume all caches are synced at this point. + IEnumerable cacheVersions = await _repositoryCacheVersionRepository.GetAllAsync(); + + foreach (RepositoryCacheVersion version in cacheVersions) + { + if (version.Version is null) + { + continue; + } + + _cacheVersions[version.Identifier] = Guid.Parse(version.Version); + } + + scope.Complete(); + } + + private string GetCacheKey() + where TEntity : class => + typeof(TEntity).Name; +} diff --git a/src/Umbraco.Core/Cache/SingleServerCacheVersionService.cs b/src/Umbraco.Core/Cache/SingleServerCacheVersionService.cs new file mode 100644 index 000000000000..d477ab6145a0 --- /dev/null +++ b/src/Umbraco.Core/Cache/SingleServerCacheVersionService.cs @@ -0,0 +1,23 @@ +namespace Umbraco.Cms.Core.Cache; + +/// +/// A simple cache version service that assumes the cache is always in sync. +/// +/// This is useful in scenarios where you have a single server setup and do not need to manage cache synchronization across multiple servers. +/// +/// +public class SingleServerCacheVersionService : IRepositoryCacheVersionService +{ + /// + public Task IsCacheSyncedAsync() + where TEntity : class + => Task.FromResult(true); + + /// + public Task SetCacheUpdatedAsync() + where TEntity : class + => Task.CompletedTask; + + /// + public Task SetCachesSyncedAsync() => Task.CompletedTask; +} diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs index 77d814579c1c..51414b4c7b68 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs @@ -340,6 +340,7 @@ private void AddCoreServices() Services.AddUnique(factory => new LocalizedTextService( factory.GetRequiredService>(), factory.GetRequiredService>())); + Services.AddUnique(); Services.AddUnique(); From 82816e10527f0affbecc3b0a7d0a4f8beddfbf73 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Mon, 14 Jul 2025 10:44:26 +0200 Subject: [PATCH 06/14] Remember to add lock in data creator --- .../Migrations/Install/DatabaseDataCreator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs index 4d01ca95d9eb..81621cbfa19b 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseDataCreator.cs @@ -1050,6 +1050,7 @@ private void CreateLockData() _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MainDom, Name = "MainDom" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.WebhookRequest, Name = "WebhookRequest" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.WebhookLogs, Name = "WebhookLogs" }); + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.CacheVersion, Name = "CacheVersion" }); } private void CreateContentTypeData() From 28bb9d3f3687862e63324f1646a0cfe27a534630 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Mon, 14 Jul 2025 11:59:53 +0200 Subject: [PATCH 07/14] Work my way out of constructor hell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is why we use DI folks. 🤦 --- .../Cache/DefaultRepositoryCachePolicy.cs | 23 +++- .../MemberRepositoryUsernameCachePolicy.cs | 23 +++- .../Cache/RepositoryCachePolicyBase.cs | 35 +++++- .../SingleItemsOnlyRepositoryCachePolicy.cs | 17 ++- .../Implement/AuditEntryRepository.cs | 12 ++- .../Repositories/Implement/AuditRepository.cs | 11 +- .../Implement/ConsentRepository.cs | 12 ++- .../Implement/ContentRepositoryBase.cs | 50 ++++++--- .../Implement/ContentTypeRepository.cs | 12 ++- .../Implement/ContentTypeRepositoryBase.cs | 13 ++- .../Implement/DataTypeContainerRepository.cs | 5 +- .../Implement/DataTypeRepository.cs | 10 +- .../Implement/DictionaryRepository.cs | 77 ++++++++++---- .../DocumentBlueprintContainerRepository.cs | 4 +- .../Implement/DocumentRepository.cs | 100 ++++++++++++------ .../DocumentTypeContainerRepository.cs | 13 ++- .../Implement/DomainRepository.cs | 8 +- .../Implement/EntityContainerRepository.cs | 10 +- .../Implement/EntityRepositoryBase.cs | 27 ++++- .../Implement/ExternalLoginRepository.cs | 9 +- .../Implement/KeyValueRepository.cs | 7 +- .../Implement/LanguageRepository.cs | 8 +- .../Implement/LogViewerQueryRepository.cs | 8 +- .../Repositories/Implement/MediaRepository.cs | 67 ++++++++++-- .../Implement/MediaTypeContainerRepository.cs | 13 ++- .../Implement/MediaTypeRepository.cs | 12 ++- .../Implement/MemberGroupRepository.cs | 10 +- .../Implement/MemberRepository.cs | 60 ++++++++++- .../Implement/MemberTypeRepository.cs | 12 ++- .../Implement/PermissionRepository.cs | 8 +- .../Implement/PublicAccessRepository.cs | 8 +- .../Implement/RedirectUrlRepository.cs | 8 +- .../Implement/RelationRepository.cs | 9 +- .../Implement/RelationTypeRepository.cs | 8 +- .../Implement/ServerRegistrationRepository.cs | 7 +- .../Implement/SimpleGetRepository.cs | 8 +- .../Repositories/Implement/TagRepository.cs | 8 +- .../Implement/TemplateRepository.cs | 5 +- .../Implement/TwoFactorLoginRepository.cs | 9 +- .../Implement/UserGroupRepository.cs | 51 +++++++-- .../Repositories/Implement/UserRepository.cs | 6 +- .../Repositories/AuditRepositoryTest.cs | 20 ++-- .../Repositories/ContentTypeRepositoryTest.cs | 3 +- .../Repositories/DocumentRepositoryTest.cs | 14 +-- .../Repositories/DomainRepositoryTest.cs | 3 +- .../Repositories/KeyValueRepositoryTests.cs | 4 +- .../Repositories/LanguageRepositoryTest.cs | 3 +- .../Repositories/MediaRepositoryTest.cs | 13 +-- .../Repositories/MediaTypeRepositoryTest.cs | 5 +- .../Repositories/MemberTypeRepositoryTest.cs | 2 +- .../PublicAccessRepositoryTest.cs | 16 +-- .../RedirectUrlRepositoryTests.cs | 4 +- .../Repositories/RelationRepositoryTest.cs | 4 +- .../RelationTypeRepositoryTest.cs | 5 +- .../ServerRegistrationRepositoryTest.cs | 3 +- .../Repositories/TagRepositoryTest.cs | 3 +- .../Repositories/TemplateRepositoryTest.cs | 12 +-- .../Repositories/UserRepositoryTest.cs | 6 +- .../Services/RedirectUrlServiceTests.cs | 7 +- .../Cache/SingleItemsOnlyCachePolicyTests.cs | 4 +- 60 files changed, 721 insertions(+), 213 deletions(-) diff --git a/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs index 0ac9f89b7924..dac79133036d 100644 --- a/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs @@ -1,6 +1,8 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; @@ -24,10 +26,27 @@ public class DefaultRepositoryCachePolicy : RepositoryCachePolicyB private static readonly TEntity[] _emptyEntities = new TEntity[0]; // const private readonly RepositoryCachePolicyOptions _options; - public DefaultRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options) - : base(cache, scopeAccessor) => + public DefaultRepositoryCachePolicy( + IAppPolicyCache cache, + IScopeAccessor scopeAccessor, + RepositoryCachePolicyOptions options, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(cache, scopeAccessor, repositoryCacheVersionService) => _options = options ?? throw new ArgumentNullException(nameof(options)); + [Obsolete("Use the constructor with RepositoryCachePolicyOptions parameter instead.")] + public DefaultRepositoryCachePolicy( + IAppPolicyCache cache, + IScopeAccessor scopeAccessor, + RepositoryCachePolicyOptions options) + : this( + cache, + scopeAccessor, + options, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + protected string EntityTypeCacheKey { get; } = $"uRepo_{typeof(TEntity).Name}_"; /// diff --git a/src/Umbraco.Infrastructure/Cache/MemberRepositoryUsernameCachePolicy.cs b/src/Umbraco.Infrastructure/Cache/MemberRepositoryUsernameCachePolicy.cs index 807c3d2d2c40..0340e492c624 100644 --- a/src/Umbraco.Infrastructure/Cache/MemberRepositoryUsernameCachePolicy.cs +++ b/src/Umbraco.Infrastructure/Cache/MemberRepositoryUsernameCachePolicy.cs @@ -1,4 +1,5 @@ -using Umbraco.Cms.Core.Models; +using System.Runtime.Versioning; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; @@ -6,7 +7,25 @@ namespace Umbraco.Cms.Core.Cache; public class MemberRepositoryUsernameCachePolicy : DefaultRepositoryCachePolicy { - public MemberRepositoryUsernameCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options) : base(cache, scopeAccessor, options) + public MemberRepositoryUsernameCachePolicy( + IAppPolicyCache cache, + IScopeAccessor scopeAccessor, + RepositoryCachePolicyOptions options, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + cache, + scopeAccessor, + options, + repositoryCacheVersionService) + { + } + + [Obsolete("Use the constructor with IRepositoryCacheVersionService instead. Scheuled for removal in V18.")] + public MemberRepositoryUsernameCachePolicy( + IAppPolicyCache cache, + IScopeAccessor scopeAccessor, + RepositoryCachePolicyOptions options) + : base(cache, scopeAccessor, options) { } diff --git a/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs b/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs index 7a43071b813e..510265c0aec6 100644 --- a/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs +++ b/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs @@ -1,6 +1,8 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Infrastructure.Scoping; @@ -18,11 +20,25 @@ public abstract class RepositoryCachePolicyBase : IRepositoryCache { private readonly IAppPolicyCache _globalCache; private readonly IScopeAccessor _scopeAccessor; + private readonly IRepositoryCacheVersionService _cacheVersionService; - protected RepositoryCachePolicyBase(IAppPolicyCache globalCache, IScopeAccessor scopeAccessor) + protected RepositoryCachePolicyBase( + IAppPolicyCache globalCache, + IScopeAccessor scopeAccessor, + IRepositoryCacheVersionService cacheVersionService) { _globalCache = globalCache ?? throw new ArgumentNullException(nameof(globalCache)); _scopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor)); + _cacheVersionService = cacheVersionService; + } + + [Obsolete("Use the constructor with IRepositoryCacheVersionService parameter instead. Scheduled for removal in V18.")] + protected RepositoryCachePolicyBase(IAppPolicyCache globalCache, IScopeAccessor scopeAccessor) + : this( + globalCache, + scopeAccessor, + StaticServiceProvider.Instance.GetRequiredService()) + { } protected IAppPolicyCache Cache @@ -68,4 +84,21 @@ protected IAppPolicyCache Cache /// public abstract void ClearAll(); + + /// + /// Ensures that the cache is synced with the database. + /// + protected void EsnureCacheIsSynced() + { + var synced = _cacheVersionService.IsCacheSyncedAsync().GetAwaiter().GetResult(); + if (synced is false) + { + // TODO: Trigger a cache refresh + } + } + + /// + /// Registers a change in the cache. + /// + protected void RegisterCacheChange() => _cacheVersionService.SetCacheUpdatedAsync().Wait(); } diff --git a/src/Umbraco.Infrastructure/Cache/SingleItemsOnlyRepositoryCachePolicy.cs b/src/Umbraco.Infrastructure/Cache/SingleItemsOnlyRepositoryCachePolicy.cs index 16079d059a4a..0e6f1c6b3af7 100644 --- a/src/Umbraco.Infrastructure/Cache/SingleItemsOnlyRepositoryCachePolicy.cs +++ b/src/Umbraco.Infrastructure/Cache/SingleItemsOnlyRepositoryCachePolicy.cs @@ -1,6 +1,8 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Infrastructure.Scoping; @@ -21,8 +23,21 @@ namespace Umbraco.Cms.Core.Cache; internal class SingleItemsOnlyRepositoryCachePolicy : DefaultRepositoryCachePolicy where TEntity : class, IEntity { + public SingleItemsOnlyRepositoryCachePolicy( + IAppPolicyCache cache, + IScopeAccessor scopeAccessor, + RepositoryCachePolicyOptions options, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(cache, scopeAccessor, options, repositoryCacheVersionService) + { + } + public SingleItemsOnlyRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options) - : base(cache, scopeAccessor, options) + : this( + cache, + scopeAccessor, + options, + StaticServiceProvider.Instance.GetRequiredService()) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs index eab408823ee2..ab7f8c7357db 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditEntryRepository.cs @@ -21,8 +21,16 @@ internal class AuditEntryRepository : EntityRepositoryBase, IA /// /// Initializes a new instance of the class. /// - public AuditEntryRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public AuditEntryRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + scopeAccessor, + cache, + logger, + repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs index e112e360d00e..513113741474 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/AuditRepository.cs @@ -14,8 +14,15 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; internal class AuditRepository : EntityRepositoryBase, IAuditRepository { - public AuditRepository(IScopeAccessor scopeAccessor, ILogger logger) - : base(scopeAccessor, AppCaches.NoCache, logger) + public AuditRepository( + IScopeAccessor scopeAccessor, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + scopeAccessor, + AppCaches.NoCache, + logger, + repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs index 74f3a419e51c..1f6a08f77ae8 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ConsentRepository.cs @@ -20,8 +20,16 @@ internal class ConsentRepository : EntityRepositoryBase, IConsent /// /// Initializes a new instance of the class. /// - public ConsentRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public ConsentRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + scopeAccessor, + cache, + logger, + repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs index ce30372d3525..dd66a06e5558 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -1,9 +1,11 @@ using System.Globalization; using System.Text.RegularExpressions; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Editors; @@ -38,21 +40,6 @@ public abstract class ContentRepositoryBase : EntityR private readonly DataValueReferenceFactoryCollection _dataValueReferenceFactories; private readonly IEventAggregator _eventAggregator; - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// Lazy property value collection - must be lazy because we have a circular dependency since some property editors require services, yet these services require property editors - /// protected ContentRepositoryBase( IScopeAccessor scopeAccessor, AppCaches cache, @@ -63,8 +50,9 @@ protected ContentRepositoryBase( PropertyEditorCollection propertyEditors, DataValueReferenceFactoryCollection dataValueReferenceFactories, IDataTypeService dataTypeService, - IEventAggregator eventAggregator) - : base(scopeAccessor, cache, logger) + IEventAggregator eventAggregator, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { DataTypeService = dataTypeService; LanguageRepository = languageRepository; @@ -75,6 +63,34 @@ protected ContentRepositoryBase( _eventAggregator = eventAggregator; } + [Obsolete("Use the constructor with IRepositoryCacheVersionService instead. Scheduled for removal in v18.")] + protected ContentRepositoryBase( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger> logger, + ILanguageRepository languageRepository, + IRelationRepository relationRepository, + IRelationTypeRepository relationTypeRepository, + PropertyEditorCollection propertyEditors, + DataValueReferenceFactoryCollection dataValueReferenceFactories, + IDataTypeService dataTypeService, + IEventAggregator eventAggregator) + : this( + scopeAccessor, + cache, + logger, + languageRepository, + relationRepository, + relationTypeRepository, + propertyEditors, + dataValueReferenceFactories, + dataTypeService, + eventAggregator, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + + protected abstract TRepository This { get; } /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs index f437c0cd0d1d..34a2ab160120 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepository.cs @@ -25,8 +25,16 @@ public ContentTypeRepository( ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository, - IShortStringHelper shortStringHelper) - : base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper) + IShortStringHelper shortStringHelper, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + scopeAccessor, + cache, + logger, + commonRepository, + languageRepository, + shortStringHelper, + repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs index 0d68cd39bf04..ce3c7a3d0401 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs @@ -30,10 +30,15 @@ internal abstract class ContentTypeRepositoryBase : EntityRepositoryBas { private readonly IShortStringHelper _shortStringHelper; - protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, - ILogger> logger, IContentTypeCommonRepository commonRepository, - ILanguageRepository languageRepository, IShortStringHelper shortStringHelper) - : base(scopeAccessor, cache, logger) + protected ContentTypeRepositoryBase( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger> logger, + IContentTypeCommonRepository commonRepository, + ILanguageRepository languageRepository, + IShortStringHelper shortStringHelper, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { _shortStringHelper = shortStringHelper; CommonRepository = commonRepository; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeContainerRepository.cs index c7321063ad4d..649912f8bcb9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeContainerRepository.cs @@ -11,8 +11,9 @@ internal class DataTypeContainerRepository : EntityContainerRepository, IDataTyp public DataTypeContainerRepository( IScopeAccessor scopeAccessor, AppCaches cache, - ILogger logger) - : base(scopeAccessor, cache, logger, Constants.ObjectTypes.DataTypeContainer) + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, Constants.ObjectTypes.DataTypeContainer, repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs index a8f6031ca8a6..f267ca9c2cfd 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DataTypeRepository.cs @@ -39,8 +39,14 @@ public DataTypeRepository( PropertyEditorCollection editors, ILogger logger, ILoggerFactory loggerFactory, - IConfigurationEditorJsonSerializer serializer) - : base(scopeAccessor, cache, logger) + IConfigurationEditorJsonSerializer serializer, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + scopeAccessor, + cache, + logger, + repositoryCacheVersionService + ) { _editors = editors; _serializer = serializer; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs index fc6c380dac8d..09990215d4e2 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DictionaryRepository.cs @@ -1,7 +1,9 @@ +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence.Querying; @@ -21,40 +23,63 @@ internal class DictionaryRepository : EntityRepositoryBase { private readonly ILoggerFactory _loggerFactory; private readonly ILanguageRepository _languageRepository; - - public DictionaryRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, - ILoggerFactory loggerFactory, ILanguageRepository languageRepository) + private readonly IRepositoryCacheVersionService _repositoryCacheVersionService; + + public DictionaryRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + ILoggerFactory loggerFactory, + ILanguageRepository languageRepository, + IRepositoryCacheVersionService repositoryCacheVersionService) : base(scopeAccessor, cache, logger) { _loggerFactory = loggerFactory; _languageRepository = languageRepository; + _repositoryCacheVersionService = repositoryCacheVersionService; } public IDictionaryItem? Get(Guid uniqueId) { - var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, ScopeAccessor, AppCaches, - _loggerFactory.CreateLogger()); + var uniqueIdRepo = new DictionaryByUniqueIdRepository( + this, + ScopeAccessor, + AppCaches, + _loggerFactory.CreateLogger(), + _repositoryCacheVersionService); return uniqueIdRepo.Get(uniqueId); } public IEnumerable GetMany(params Guid[] uniqueIds) { - var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, ScopeAccessor, AppCaches, - _loggerFactory.CreateLogger()); + var uniqueIdRepo = new DictionaryByUniqueIdRepository( + this, + ScopeAccessor, + AppCaches, + _loggerFactory.CreateLogger(), + _repositoryCacheVersionService); return uniqueIdRepo.GetMany(uniqueIds); } public IDictionaryItem? Get(string key) { - var keyRepo = new DictionaryByKeyRepository(this, ScopeAccessor, AppCaches, - _loggerFactory.CreateLogger()); + var keyRepo = new DictionaryByKeyRepository( + this, + ScopeAccessor, + AppCaches, + _loggerFactory.CreateLogger(), + _repositoryCacheVersionService); return keyRepo.Get(key); } public IEnumerable GetManyByKeys(string[] keys) { - var keyRepo = new DictionaryByKeyRepository(this, ScopeAccessor, AppCaches, - _loggerFactory.CreateLogger()); + var keyRepo = new DictionaryByKeyRepository( + this, + ScopeAccessor, + AppCaches, + _loggerFactory.CreateLogger(), + _repositoryCacheVersionService); return keyRepo.GetMany(keys); } @@ -125,7 +150,7 @@ protected override IRepositoryCachePolicy CreateCachePolic GetAllCacheAllowZeroCount = true }; - return new SingleItemsOnlyRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, options); + return new SingleItemsOnlyRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, options, _repositoryCacheVersionService); } private IDictionaryItem ConvertFromDto(DictionaryDto dto, IDictionary languagesById) @@ -184,13 +209,19 @@ private class DictionaryItemKeyIdDto private class DictionaryByUniqueIdRepository : SimpleGetRepository { private readonly DictionaryRepository _dictionaryRepository; + private readonly IRepositoryCacheVersionService _repositoryCacheVersionService; private readonly IDictionary _languagesById; - public DictionaryByUniqueIdRepository(DictionaryRepository dictionaryRepository, IScopeAccessor scopeAccessor, - AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public DictionaryByUniqueIdRepository( + DictionaryRepository dictionaryRepository, + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { _dictionaryRepository = dictionaryRepository; + _repositoryCacheVersionService = repositoryCacheVersionService; _languagesById = dictionaryRepository.GetLanguagesById(); } @@ -219,7 +250,7 @@ protected override IRepositoryCachePolicy CreateCachePoli GetAllCacheAllowZeroCount = true }; - return new SingleItemsOnlyRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, options); + return new SingleItemsOnlyRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, options, _repositoryCacheVersionService); } protected override IEnumerable PerformGetAll(params Guid[]? ids) @@ -239,13 +270,19 @@ protected override IEnumerable PerformGetAll(params Guid[]? ids private class DictionaryByKeyRepository : SimpleGetRepository { private readonly DictionaryRepository _dictionaryRepository; + private readonly IRepositoryCacheVersionService _repositoryCacheVersionService; private readonly IDictionary _languagesById; - public DictionaryByKeyRepository(DictionaryRepository dictionaryRepository, IScopeAccessor scopeAccessor, - AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public DictionaryByKeyRepository( + DictionaryRepository dictionaryRepository, + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { _dictionaryRepository = dictionaryRepository; + _repositoryCacheVersionService = repositoryCacheVersionService; _languagesById = dictionaryRepository.GetLanguagesById(); } @@ -276,7 +313,7 @@ protected override IRepositoryCachePolicy CreateCachePo GetAllCacheAllowZeroCount = true }; - return new SingleItemsOnlyRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, options); + return new SingleItemsOnlyRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, options, _repositoryCacheVersionService); } protected override IEnumerable PerformGetAll(params string[]? ids) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentBlueprintContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentBlueprintContainerRepository.cs index 720f94d04e9a..6da5242deaa0 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentBlueprintContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentBlueprintContainerRepository.cs @@ -11,8 +11,8 @@ internal class DocumentBlueprintContainerRepository : EntityContainerRepository, public DocumentBlueprintContainerRepository( IScopeAccessor scopeAccessor, AppCaches cache, - ILogger logger) - : base(scopeAccessor, cache, logger, Constants.ObjectTypes.DocumentBlueprintContainer) + ILogger logger, IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, Constants.ObjectTypes.DocumentBlueprintContainer, repositoryCacheVersionService) { } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs index 1b4f2b1efda3..697f0bc44635 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs @@ -1,8 +1,10 @@ using System.Globalization; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; @@ -33,31 +35,11 @@ public class DocumentRepository : ContentRepositoryBase? _permissionRepository; - /// - /// Constructor - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// Lazy property value collection - must be lazy because we have a circular dependency since some property editors - /// require services, yet these services require property editors - /// public DocumentRepository( IScopeAccessor scopeAccessor, AppCaches appCaches, @@ -73,20 +55,73 @@ public DocumentRepository( DataValueReferenceFactoryCollection dataValueReferenceFactories, IDataTypeService dataTypeService, IJsonSerializer serializer, - IEventAggregator eventAggregator) - : base(scopeAccessor, appCaches, logger, languageRepository, relationRepository, relationTypeRepository, - propertyEditors, dataValueReferenceFactories, dataTypeService, eventAggregator) + IEventAggregator eventAggregator, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + scopeAccessor, + appCaches, + logger, + languageRepository, + relationRepository, + relationTypeRepository, + propertyEditors, + dataValueReferenceFactories, + dataTypeService, + eventAggregator, + repositoryCacheVersionService) { _contentTypeRepository = contentTypeRepository ?? throw new ArgumentNullException(nameof(contentTypeRepository)); _templateRepository = templateRepository ?? throw new ArgumentNullException(nameof(templateRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); _serializer = serializer; + _repositoryCacheVersionService = repositoryCacheVersionService; _appCaches = appCaches; _loggerFactory = loggerFactory; _scopeAccessor = scopeAccessor; - _contentByGuidReadRepository = new ContentByGuidReadRepository(this, scopeAccessor, appCaches, - loggerFactory.CreateLogger()); + _contentByGuidReadRepository = new ContentByGuidReadRepository( + this, + scopeAccessor, + appCaches, + loggerFactory.CreateLogger(), + repositoryCacheVersionService); + } + + [Obsolete("Use constructor with IRepositoryCacheVersionService instead. Scheduled for removal in v18.")] + public DocumentRepository( + IScopeAccessor scopeAccessor, + AppCaches appCaches, + ILogger logger, + ILoggerFactory loggerFactory, + IContentTypeRepository contentTypeRepository, + ITemplateRepository templateRepository, + ITagRepository tagRepository, + ILanguageRepository languageRepository, + IRelationRepository relationRepository, + IRelationTypeRepository relationTypeRepository, + PropertyEditorCollection propertyEditors, + DataValueReferenceFactoryCollection dataValueReferenceFactories, + IDataTypeService dataTypeService, + IJsonSerializer serializer, + IEventAggregator eventAggregator) + : this( + scopeAccessor, + appCaches, + logger, + loggerFactory, + contentTypeRepository, + templateRepository, + tagRepository, + languageRepository, + relationRepository, + relationTypeRepository, + propertyEditors, + dataValueReferenceFactories, + dataTypeService, + serializer, + eventAggregator, + StaticServiceProvider.Instance.GetRequiredService()) + { } protected override DocumentRepository This => this; @@ -101,7 +136,8 @@ public DocumentRepository( new PermissionRepository( _scopeAccessor, _appCaches, - _loggerFactory.CreateLogger>()); + _loggerFactory.CreateLogger>(), + _repositoryCacheVersionService); /// public ContentScheduleCollection GetContentSchedule(int contentId) @@ -1557,9 +1593,13 @@ private class ContentByGuidReadRepository : EntityRepositoryBase { private readonly DocumentRepository _outerRepo; - public ContentByGuidReadRepository(DocumentRepository outerRepo, IScopeAccessor scopeAccessor, AppCaches cache, - ILogger logger) - : base(scopeAccessor, cache, logger) => + public ContentByGuidReadRepository( + DocumentRepository outerRepo, + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) => _outerRepo = outerRepo; protected override IContent? PerformGet(Guid id) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentTypeContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentTypeContainerRepository.cs index 1f57f89420b3..f798ba5f314b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentTypeContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentTypeContainerRepository.cs @@ -8,8 +8,17 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; internal class DocumentTypeContainerRepository : EntityContainerRepository, IDocumentTypeContainerRepository { - public DocumentTypeContainerRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger, Constants.ObjectTypes.DocumentTypeContainer) + public DocumentTypeContainerRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + scopeAccessor, + cache, + logger, + Constants.ObjectTypes.DocumentTypeContainer, + repositoryCacheVersionService) { } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs index b1ab5f94377c..a65b94b45891 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DomainRepository.cs @@ -15,8 +15,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; internal class DomainRepository : EntityRepositoryBase, IDomainRepository { - public DomainRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public DomainRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { } public IDomain? GetByName(string domainName) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs index cb1cf56a7226..f65acc6040b2 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityContainerRepository.cs @@ -16,9 +16,13 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; /// internal class EntityContainerRepository : EntityRepositoryBase, IEntityContainerRepository { - public EntityContainerRepository(IScopeAccessor scopeAccessor, AppCaches cache, - ILogger logger, Guid containerObjectType) - : base(scopeAccessor, cache, logger) + public EntityContainerRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + Guid containerObjectType, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { Guid[] allowedContainers = { diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs index aeb8f1766e10..48b5818c137d 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/EntityRepositoryBase.cs @@ -1,7 +1,9 @@ +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Persistence; using Umbraco.Cms.Core.Persistence.Querying; @@ -20,6 +22,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; public abstract class EntityRepositoryBase : RepositoryBase, IReadWriteQueryRepository where TEntity : class, IEntity { + private readonly IRepositoryCacheVersionService _repositoryCacheVersionService; private static RepositoryCachePolicyOptions? _defaultOptions; private IRepositoryCachePolicy? _cachePolicy; private IQuery? _hasIdQuery; @@ -27,9 +30,27 @@ public abstract class EntityRepositoryBase : RepositoryBase, IRead /// /// Initializes a new instance of the class. /// - protected EntityRepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger> logger) - : base(scopeAccessor, appCaches) => + protected EntityRepositoryBase( + IScopeAccessor scopeAccessor, + AppCaches appCaches, + ILogger> logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, appCaches) + { + _repositoryCacheVersionService = repositoryCacheVersionService; Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + [Obsolete("Use the constructor with IRepositoryCacheVersionService instead. Scheduled for removal in v18.")] + protected EntityRepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, + ILogger> logger) + : this( + scopeAccessor, + appCaches, + logger, + StaticServiceProvider.Instance.GetRequiredService()) + { + } /// /// Gets the logger @@ -194,7 +215,7 @@ protected virtual TId GetEntityId(TEntity entity) /// Create the repository cache policy /// protected virtual IRepositoryCachePolicy CreateCachePolicy() - => new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); + => new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions, _repositoryCacheVersionService); protected abstract TEntity? PerformGet(TId? id); diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs index 1ba91ab3e1d6..c10f4a765058 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ExternalLoginRepository.cs @@ -16,9 +16,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; internal class ExternalLoginRepository : EntityRepositoryBase, IExternalLoginWithKeyRepository { - public ExternalLoginRepository(IScopeAccessor scopeAccessor, AppCaches cache, - ILogger logger) - : base(scopeAccessor, cache, logger) + public ExternalLoginRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { } /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs index c7259df863b4..3c1a20880eba 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/KeyValueRepository.cs @@ -14,8 +14,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; internal class KeyValueRepository : EntityRepositoryBase, IKeyValueRepository { - public KeyValueRepository(IScopeAccessor scopeAccessor, ILogger logger) - : base(scopeAccessor, AppCaches.NoCache, logger) + public KeyValueRepository( + IScopeAccessor scopeAccessor, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, AppCaches.NoCache, logger, repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs index 5097c99fa410..fb498ed0447f 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LanguageRepository.cs @@ -24,8 +24,12 @@ internal class LanguageRepository : EntityRepositoryBase, ILangu private readonly Dictionary _codeIdMap = new(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _idCodeMap = new(); - public LanguageRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public LanguageRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs index a00c35de6d12..40706911cc53 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/LogViewerQueryRepository.cs @@ -14,8 +14,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; internal class LogViewerQueryRepository : EntityRepositoryBase, ILogViewerQueryRepository { - public LogViewerQueryRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public LogViewerQueryRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs index 2f037653a770..4384c12448b6 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaRepository.cs @@ -1,8 +1,10 @@ using System.Text.RegularExpressions; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; @@ -48,9 +50,20 @@ public MediaRepository( DataValueReferenceFactoryCollection dataValueReferenceFactories, IDataTypeService dataTypeService, IJsonSerializer serializer, - IEventAggregator eventAggregator) - : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, - propertyEditorCollection, dataValueReferenceFactories, dataTypeService, eventAggregator) + IEventAggregator eventAggregator, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + scopeAccessor, + cache, + logger, + languageRepository, + relationRepository, + relationTypeRepository, + propertyEditorCollection, + dataValueReferenceFactories, + dataTypeService, + eventAggregator, + repositoryCacheVersionService) { _cache = cache; _mediaTypeRepository = mediaTypeRepository ?? throw new ArgumentNullException(nameof(mediaTypeRepository)); @@ -61,7 +74,44 @@ public MediaRepository( this, scopeAccessor, cache, - loggerFactory.CreateLogger()); + loggerFactory.CreateLogger(), + repositoryCacheVersionService); + } + + public MediaRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + ILoggerFactory loggerFactory, + IMediaTypeRepository mediaTypeRepository, + ITagRepository tagRepository, + ILanguageRepository languageRepository, + IRelationRepository relationRepository, + IRelationTypeRepository relationTypeRepository, + PropertyEditorCollection propertyEditorCollection, + MediaUrlGeneratorCollection mediaUrlGenerators, + DataValueReferenceFactoryCollection dataValueReferenceFactories, + IDataTypeService dataTypeService, + IJsonSerializer serializer, + IEventAggregator eventAggregator) + : this(scopeAccessor, + cache, + logger, + loggerFactory, + mediaTypeRepository, + tagRepository, + languageRepository, + relationRepository, + relationTypeRepository, + propertyEditorCollection, + mediaUrlGenerators, + dataValueReferenceFactories, + dataTypeService, + serializer, + eventAggregator, + StaticServiceProvider.Instance.GetRequiredService() + ) + { } protected override MediaRepository This => this; @@ -526,8 +576,13 @@ private class MediaByGuidReadRepository : EntityRepositoryBase { private readonly MediaRepository _outerRepo; - public MediaByGuidReadRepository(MediaRepository outerRepo, IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) => + public MediaByGuidReadRepository( + MediaRepository outerRepo, + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) => _outerRepo = outerRepo; protected override IMedia? PerformGet(Guid id) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeContainerRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeContainerRepository.cs index 260cebef9f4f..5effbd96049e 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeContainerRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeContainerRepository.cs @@ -8,8 +8,17 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; internal class MediaTypeContainerRepository : EntityContainerRepository, IMediaTypeContainerRepository { - public MediaTypeContainerRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger, Constants.ObjectTypes.MediaTypeContainer) + public MediaTypeContainerRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + scopeAccessor, + cache, + logger, + Constants.ObjectTypes.MediaTypeContainer, + repositoryCacheVersionService) { } } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs index 51a4c367527c..5b2169ddcdd8 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MediaTypeRepository.cs @@ -24,8 +24,16 @@ public MediaTypeRepository( ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository, - IShortStringHelper shortStringHelper) - : base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper) + IShortStringHelper shortStringHelper, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + scopeAccessor, + cache, + logger, + commonRepository, + languageRepository, + shortStringHelper, + repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs index 96d797b0577c..78cc9cd1a796 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberGroupRepository.cs @@ -19,9 +19,13 @@ internal class MemberGroupRepository : EntityRepositoryBase, { private readonly IEventMessagesFactory _eventMessagesFactory; - public MemberGroupRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, - IEventMessagesFactory eventMessagesFactory) - : base(scopeAccessor, cache, logger) => + public MemberGroupRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IEventMessagesFactory eventMessagesFactory, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) => _eventMessagesFactory = eventMessagesFactory; protected Guid NodeObjectTypeId => Constants.ObjectTypes.MemberGroup; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs index beff74a5e3d5..ad83638aeeca 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberRepository.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; @@ -5,6 +6,7 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; @@ -56,9 +58,20 @@ public MemberRepository( IDataTypeService dataTypeService, IJsonSerializer serializer, IEventAggregator eventAggregator, - IOptions passwordConfiguration) - : base(scopeAccessor, cache, logger, languageRepository, relationRepository, relationTypeRepository, - propertyEditors, dataValueReferenceFactories, dataTypeService, eventAggregator) + IOptions passwordConfiguration, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + scopeAccessor, + cache, + logger, + languageRepository, + relationRepository, + relationTypeRepository, + propertyEditors, + dataValueReferenceFactories, + dataTypeService, + eventAggregator, + repositoryCacheVersionService) { _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); @@ -68,7 +81,46 @@ public MemberRepository( _memberGroupRepository = memberGroupRepository; _passwordConfiguration = passwordConfiguration.Value; _memberByUsernameCachePolicy = - new MemberRepositoryUsernameCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); + new MemberRepositoryUsernameCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions, repositoryCacheVersionService); + } + + [Obsolete("Use the constructor with IRepositoryCacheVersionService parameter instead. Scheduled for removal in v18.")] + public MemberRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IMemberTypeRepository memberTypeRepository, + IMemberGroupRepository memberGroupRepository, + ITagRepository tagRepository, + ILanguageRepository languageRepository, + IRelationRepository relationRepository, + IRelationTypeRepository relationTypeRepository, + IPasswordHasher passwordHasher, + PropertyEditorCollection propertyEditors, + DataValueReferenceFactoryCollection dataValueReferenceFactories, + IDataTypeService dataTypeService, + IJsonSerializer serializer, + IEventAggregator eventAggregator, + IOptions passwordConfiguration) + : this( + scopeAccessor, + cache, + logger, + memberTypeRepository, + memberGroupRepository, + tagRepository, + languageRepository, + relationRepository, + relationTypeRepository, + passwordHasher, + propertyEditors, + dataValueReferenceFactories, + dataTypeService, + serializer, + eventAggregator, + passwordConfiguration, + StaticServiceProvider.Instance.GetRequiredService()) + { } /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs index 61127b766bad..cca4b11e1494 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberTypeRepository.cs @@ -27,8 +27,16 @@ public MemberTypeRepository( ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository, - IShortStringHelper shortStringHelper) - : base(scopeAccessor, cache, logger, commonRepository, languageRepository, shortStringHelper) => + IShortStringHelper shortStringHelper, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + scopeAccessor, + cache, + logger, + commonRepository, + languageRepository, + shortStringHelper, + repositoryCacheVersionService) => _shortStringHelper = shortStringHelper; protected override bool SupportsPublishing => MemberType.SupportsPublishingConst; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs index c99ac14fce0f..c86218d9d9c1 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PermissionRepository.cs @@ -26,8 +26,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; internal class PermissionRepository : EntityRepositoryBase where TEntity : class, IEntity { - public PermissionRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger> logger) - : base(scopeAccessor, cache, logger) + public PermissionRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger> logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs index 2716df93158e..9b4f898c1232 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/PublicAccessRepository.cs @@ -15,8 +15,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; internal class PublicAccessRepository : EntityRepositoryBase, IPublicAccessRepository { - public PublicAccessRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public PublicAccessRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs index f598df716805..225f917619a6 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RedirectUrlRepository.cs @@ -14,8 +14,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; internal class RedirectUrlRepository : EntityRepositoryBase, IRedirectUrlRepository { - public RedirectUrlRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public RedirectUrlRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs index 5c403445e035..8bc6595ca2a5 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationRepository.cs @@ -26,8 +26,13 @@ internal class RelationRepository : EntityRepositoryBase, IRelat private readonly IEntityRepositoryExtended _entityRepository; private readonly IRelationTypeRepository _relationTypeRepository; - public RelationRepository(IScopeAccessor scopeAccessor, ILogger logger, IRelationTypeRepository relationTypeRepository, IEntityRepositoryExtended entityRepository) - : base(scopeAccessor, AppCaches.NoCache, logger) + public RelationRepository( + IScopeAccessor scopeAccessor, + ILogger logger, + IRelationTypeRepository relationTypeRepository, + IEntityRepositoryExtended entityRepository, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, AppCaches.NoCache, logger, repositoryCacheVersionService) { _relationTypeRepository = relationTypeRepository; _entityRepository = entityRepository; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs index 8c84f2ae6597..c2013773bfb8 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RelationTypeRepository.cs @@ -19,8 +19,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; /// internal sealed class RelationTypeRepository : EntityRepositoryBase, IRelationTypeRepository { - public RelationTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public RelationTypeRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs index b758150de1a4..ead86938a68a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/ServerRegistrationRepository.cs @@ -14,8 +14,11 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; internal class ServerRegistrationRepository : EntityRepositoryBase, IServerRegistrationRepository { - public ServerRegistrationRepository(IScopeAccessor scopeAccessor, ILogger logger) - : base(scopeAccessor, AppCaches.NoCache, logger) + public ServerRegistrationRepository( + IScopeAccessor scopeAccessor, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, AppCaches.NoCache, logger, repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs index ad509afd5a57..970051b17404 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/SimpleGetRepository.cs @@ -18,8 +18,12 @@ internal abstract class SimpleGetRepository : EntityReposito where TEntity : class, IEntity where TDto : class { - protected SimpleGetRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger> logger) - : base(scopeAccessor, cache, logger) + protected SimpleGetRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger> logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs index d0d688d33296..8c98a0d31557 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TagRepository.cs @@ -17,8 +17,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; internal class TagRepository : EntityRepositoryBase, ITagRepository { - public TagRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger) - : base(scopeAccessor, cache, logger) + public TagRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs index 94f184c92d10..7019f5242156 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs @@ -38,8 +38,9 @@ public TemplateRepository( IIOHelper ioHelper, IShortStringHelper shortStringHelper, IViewHelper viewHelper, - IOptionsMonitor runtimeSettings) - : base(scopeAccessor, cache, logger) + IOptionsMonitor runtimeSettings, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { _ioHelper = ioHelper; _shortStringHelper = shortStringHelper; diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TwoFactorLoginRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TwoFactorLoginRepository.cs index 9467ec9be4e3..5b75c09a24d5 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TwoFactorLoginRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TwoFactorLoginRepository.cs @@ -14,9 +14,12 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; internal class TwoFactorLoginRepository : EntityRepositoryBase, ITwoFactorLoginRepository { - public TwoFactorLoginRepository(IScopeAccessor scopeAccessor, AppCaches cache, - ILogger logger) - : base(scopeAccessor, cache, logger) + public TwoFactorLoginRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, cache, logger, repositoryCacheVersionService) { } diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs index 1eb3fa47a7dc..1458bb3a444a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserGroupRepository.cs @@ -1,8 +1,10 @@ using System.Collections; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; using Umbraco.Cms.Core.Models.Membership; @@ -35,15 +37,43 @@ public UserGroupRepository( ILogger logger, ILoggerFactory loggerFactory, IShortStringHelper shortStringHelper, - IEnumerable permissionMappers) - : base(scopeAccessor, appCaches, logger) + IEnumerable permissionMappers, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, appCaches, logger, repositoryCacheVersionService) { _shortStringHelper = shortStringHelper; - _userGroupWithUsersRepository = new UserGroupWithUsersRepository(this, scopeAccessor, appCaches, loggerFactory.CreateLogger()); - _permissionRepository = new PermissionRepository(scopeAccessor, appCaches, loggerFactory.CreateLogger>()); + _userGroupWithUsersRepository = new UserGroupWithUsersRepository( + this, + scopeAccessor, + appCaches, + loggerFactory.CreateLogger(), + repositoryCacheVersionService); + _permissionRepository = new PermissionRepository( + scopeAccessor, + appCaches, + loggerFactory.CreateLogger>(), + repositoryCacheVersionService); _permissionMappers = permissionMappers.ToDictionary(x => x.Context); } + public UserGroupRepository( + IScopeAccessor scopeAccessor, + AppCaches appCaches, + ILogger logger, + ILoggerFactory loggerFactory, + IShortStringHelper shortStringHelper, + IEnumerable permissionMappers) + : this( + scopeAccessor, + appCaches, + logger, + loggerFactory, + shortStringHelper, + permissionMappers, + StaticServiceProvider.Instance.GetRequiredService()) + { + } + public IUserGroup? Get(string alias) { try @@ -205,8 +235,17 @@ private class UserGroupWithUsersRepository : EntityRepositoryBase logger) - : base(scopeAccessor, cache, logger) => + public UserGroupWithUsersRepository( + UserGroupRepository userGroupRepo, + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base( + scopeAccessor, + cache, + logger, + repositoryCacheVersionService) => _userGroupRepo = userGroupRepo; protected override void PersistNewItem(UserGroupWithUsers entity) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs index 187d5c5ce2cd..e9f5e2f7fa12 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/UserRepository.cs @@ -55,6 +55,7 @@ internal class UserRepository : EntityRepositoryBase, IUserReposito /// State of the runtime. /// The permission mappers. /// The app policy cache. + /// /// /// mapperCollection /// or @@ -72,8 +73,9 @@ public UserRepository( IJsonSerializer jsonSerializer, IRuntimeState runtimeState, IEnumerable permissionMappers, - IAppPolicyCache globalCache) - : base(scopeAccessor, appCaches, logger) + IAppPolicyCache globalCache, + IRepositoryCacheVersionService repositoryCacheVersionService) + : base(scopeAccessor, appCaches, logger, repositoryCacheVersionService) { _scopeAccessor = scopeAccessor; _mapperCollection = mapperCollection ?? throw new ArgumentNullException(nameof(mapperCollection)); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/AuditRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/AuditRepositoryTest.cs index e206abbd4446..de9121f44428 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/AuditRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/AuditRepositoryTest.cs @@ -2,8 +2,10 @@ // See LICENSE for more details. using Microsoft.Extensions.Logging; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Infrastructure.Persistence; @@ -34,7 +36,7 @@ public void Can_Add_Audit_Entry() var sp = ScopeProvider; using (var scope = ScopeProvider.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor)sp, _logger); + var repo = new AuditRepository((IScopeAccessor)sp, _logger, Mock.Of()); repo.Save(new AuditItem(-1, AuditType.System, -1, UmbracoObjectTypes.Document.GetName(), "This is a System audit trail")); var dtos = ScopeAccessor.AmbientScope.Database.Fetch("WHERE id > -1"); @@ -82,7 +84,7 @@ public void Get_Paged_Items() var sp = ScopeProvider; using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor)sp, _logger); + var repo = new AuditRepository((IScopeAccessor)sp, _logger, Mock.Of()); for (var i = 0; i < 100; i++) { @@ -95,7 +97,7 @@ public void Get_Paged_Items() using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor)sp, _logger); + var repo = new AuditRepository((IScopeAccessor)sp, _logger, Mock.Of()); var page = repo.GetPagedResultsByQuery(sp.CreateQuery(), 0, 10, out var total, Direction.Descending, null, null); @@ -110,7 +112,7 @@ public void Get_Paged_Items_By_User_Id_With_Query_And_Filter() var sp = ScopeProvider; using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor)sp, _logger); + var repo = new AuditRepository((IScopeAccessor)sp, _logger, Mock.Of()); for (var i = 0; i < 100; i++) { @@ -123,7 +125,7 @@ public void Get_Paged_Items_By_User_Id_With_Query_And_Filter() using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor)sp, _logger); + var repo = new AuditRepository((IScopeAccessor)sp, _logger, Mock.Of()); var query = sp.CreateQuery().Where(x => x.UserId == -1); @@ -159,7 +161,7 @@ public void Get_Paged_Items_With_AuditType_Filter() var sp = ScopeProvider; using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor)sp, _logger); + var repo = new AuditRepository((IScopeAccessor)sp, _logger, Mock.Of()); for (var i = 0; i < 100; i++) { @@ -172,7 +174,7 @@ public void Get_Paged_Items_With_AuditType_Filter() using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor)sp, _logger); + var repo = new AuditRepository((IScopeAccessor)sp, _logger, Mock.Of()); var page = repo.GetPagedResultsByQuery( sp.CreateQuery(), @@ -196,7 +198,7 @@ public void Get_Paged_Items_With_Custom_Filter() var sp = ScopeProvider; using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor)sp, _logger); + var repo = new AuditRepository((IScopeAccessor)sp, _logger, Mock.Of()); for (var i = 0; i < 100; i++) { @@ -209,7 +211,7 @@ public void Get_Paged_Items_With_Custom_Filter() using (var scope = sp.CreateScope()) { - var repo = new AuditRepository((IScopeAccessor)sp, _logger); + var repo = new AuditRepository((IScopeAccessor)sp, _logger, Mock.Of()); var page = repo.GetPagedResultsByQuery( sp.CreateQuery(), diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs index 17cbab42edf0..ed0ddcda687b 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -90,7 +90,8 @@ public void Maps_Templates_Correctly() IOHelper, ShortStringHelper, Mock.Of(), - runtimeSettingsMock.Object); + runtimeSettingsMock.Object, + Mock.Of()); var repository = ContentTypeRepository; Template[] templates = { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs index 94a6c1884abb..205e333c7ade 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs @@ -103,7 +103,7 @@ private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out Co var ctRepository = CreateRepository(scopeAccessor, out contentTypeRepository, out TemplateRepository tr); var editors = new PropertyEditorCollection(new DataEditorCollection(() => Enumerable.Empty())); - dtdRepository = new DataTypeRepository(scopeAccessor, appCaches, editors, LoggerFactory.CreateLogger(), LoggerFactory, ConfigurationEditorJsonSerializer); + dtdRepository = new DataTypeRepository(scopeAccessor, appCaches, editors, LoggerFactory.CreateLogger(), LoggerFactory, ConfigurationEditorJsonSerializer, Mock.Of()); return ctRepository; } @@ -117,16 +117,16 @@ private DocumentRepository CreateRepository(IScopeAccessor scopeAccessor, out Co var runtimeSettingsMock = new Mock>(); runtimeSettingsMock.Setup(x => x.CurrentValue).Returns(new RuntimeSettings()); - templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, Mock.Of(), runtimeSettingsMock.Object); - var tagRepository = new TagRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger()); + templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, Mock.Of(), runtimeSettingsMock.Object, Mock.Of()); + var tagRepository = new TagRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), Mock.Of()); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches, ShortStringHelper); var languageRepository = - new LanguageRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger()); - contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper); - var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger()); + new LanguageRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), Mock.Of()); + contentTypeRepository = new ContentTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper, Mock.Of()); + var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger(), Mock.Of()); var entityRepository = new EntityRepository(scopeAccessor, AppCaches.Disabled); - var relationRepository = new RelationRepository(scopeAccessor, LoggerFactory.CreateLogger(), relationTypeRepository, entityRepository); + var relationRepository = new RelationRepository(scopeAccessor, LoggerFactory.CreateLogger(), relationTypeRepository, entityRepository, Mock.Of()); var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(() => Enumerable.Empty())); var dataValueReferences = diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DomainRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DomainRepositoryTest.cs index e0885097af71..edb36541b541 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DomainRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DomainRepositoryTest.cs @@ -5,6 +5,7 @@ using System.Data; using System.Linq; using Microsoft.Extensions.Logging; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; @@ -32,7 +33,7 @@ private DomainRepository CreateRepository(IScopeProvider provider) { var accessor = (IScopeAccessor)provider; var domainRepository = - new DomainRepository(accessor, AppCaches.NoCache, LoggerFactory.CreateLogger()); + new DomainRepository(accessor, AppCaches.NoCache, LoggerFactory.CreateLogger(), Mock.Of()); return domainRepository; } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/KeyValueRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/KeyValueRepositoryTests.cs index f860c11c7e05..792712a2d59c 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/KeyValueRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/KeyValueRepositoryTests.cs @@ -2,7 +2,9 @@ // See LICENSE for more details. using Microsoft.Extensions.Logging; +using Moq; using NUnit.Framework; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Scoping; @@ -64,5 +66,5 @@ public void CanSetAndGet() } private IKeyValueRepository CreateRepository(ICoreScopeProvider provider) => - new KeyValueRepository((IScopeAccessor)provider, LoggerFactory.CreateLogger()); + new KeyValueRepository((IScopeAccessor)provider, LoggerFactory.CreateLogger(), Mock.Of()); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/LanguageRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/LanguageRepositoryTest.cs index 676a74fe40db..8a2adb1f3aad 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/LanguageRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/LanguageRepositoryTest.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using Microsoft.Extensions.Logging; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; @@ -365,7 +366,7 @@ public void Can_Perform_Exists_On_LanguageRepository() } } - private LanguageRepository CreateRepository(IScopeProvider provider) => new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger()); + private LanguageRepository CreateRepository(IScopeProvider provider) => new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), Mock.Of()); private async Task CreateTestData() { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs index 3b21605d071e..dc1bf3b03678 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaRepositoryTest.cs @@ -55,12 +55,12 @@ private MediaRepository CreateRepository(IScopeProvider provider, out MediaTypeR var commonRepository = new ContentTypeCommonRepository(scopeAccessor, TemplateRepository, appCaches, ShortStringHelper); var languageRepository = - new LanguageRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger()); - mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper); - var tagRepository = new TagRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger()); - var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger()); + new LanguageRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), Mock.Of()); + mediaTypeRepository = new MediaTypeRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper, Mock.Of()); + var tagRepository = new TagRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), Mock.Of()); + var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger(), Mock.Of()); var entityRepository = new EntityRepository(scopeAccessor, AppCaches.Disabled); - var relationRepository = new RelationRepository(scopeAccessor, LoggerFactory.CreateLogger(), relationTypeRepository, entityRepository); + var relationRepository = new RelationRepository(scopeAccessor, LoggerFactory.CreateLogger(), relationTypeRepository, entityRepository, Mock.Of()); var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(() => Enumerable.Empty())); var mediaUrlGenerators = new MediaUrlGeneratorCollection(() => Enumerable.Empty()); @@ -81,7 +81,8 @@ private MediaRepository CreateRepository(IScopeProvider provider, out MediaTypeR dataValueReferences, DataTypeService, JsonSerializer, - Mock.Of()); + Mock.Of(), + Mock.Of()); return repository; } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaTypeRepositoryTest.cs index 34a92456363b..f06c76299028 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -3,6 +3,7 @@ using System.Linq; using Microsoft.Extensions.Logging; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; @@ -411,8 +412,8 @@ public void Can_Verify_PropertyTypes_On_File_MediaType() } private MediaTypeRepository CreateRepository(IScopeProvider provider) => - new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), CommonRepository, LanguageRepository, ShortStringHelper); + new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), CommonRepository, LanguageRepository, ShortStringHelper, Mock.Of()); private EntityContainerRepository CreateContainerRepository(IScopeProvider provider) => - new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), Constants.ObjectTypes.MediaTypeContainer); + new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), Constants.ObjectTypes.MediaTypeContainer, Mock.Of()); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs index 0c1f07123989..a021ef9181c5 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -26,7 +26,7 @@ private MemberTypeRepository CreateRepository(IScopeProvider provider) { var commonRepository = GetRequiredService(); var languageRepository = GetRequiredService(); - return new MemberTypeRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of>(), commonRepository, languageRepository, ShortStringHelper); + return new MemberTypeRepository((IScopeAccessor)provider, AppCaches.Disabled, Mock.Of>(), commonRepository, languageRepository, ShortStringHelper, Mock.Of()); } [Test] diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PublicAccessRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PublicAccessRepositoryTest.cs index 75d18639d342..1b4aa09b82da 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PublicAccessRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/PublicAccessRepositoryTest.cs @@ -4,7 +4,9 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; +using Moq; using NUnit.Framework; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Infrastructure.Persistence; @@ -33,7 +35,7 @@ public void Can_Delete() var provider = ScopeProvider; using (var scope = provider.CreateScope()) { - var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger()); + var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger(), Mock.Of()); PublicAccessRule[] rules = { new PublicAccessRule { RuleValue = "test", RuleType = "RoleName" } }; var entry = new PublicAccessEntry(content[0], content[1], content[2], rules); @@ -55,7 +57,7 @@ public void Can_Add() using (var scope = provider.CreateScope()) { ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; - var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger()); + var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger(), Mock.Of()); PublicAccessRule[] rules = { new PublicAccessRule { RuleValue = "test", RuleType = "RoleName" } }; var entry = new PublicAccessEntry(content[0], content[1], content[2], rules); @@ -89,7 +91,7 @@ public void Can_Add2() using (var scope = provider.CreateScope()) { ScopeAccessor.AmbientScope.Database.AsUmbracoDatabase().EnableSqlTrace = true; - var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger()); + var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger(), Mock.Of()); PublicAccessRule[] rules = { @@ -123,7 +125,7 @@ public void Can_Update() var provider = ScopeProvider; using (var scope = provider.CreateScope()) { - var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger()); + var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger(), Mock.Of()); PublicAccessRule[] rules = { new PublicAccessRule { RuleValue = "test", RuleType = "RoleName" } }; var entry = new PublicAccessEntry(content[0], content[1], content[2], rules); @@ -152,7 +154,7 @@ public void Get_By_Id() var provider = ScopeProvider; using (var scope = provider.CreateScope()) { - var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger()); + var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger(),Mock.Of()); PublicAccessRule[] rules = { new PublicAccessRule { RuleValue = "test", RuleType = "RoleName" } }; var entry = new PublicAccessEntry(content[0], content[1], content[2], rules); @@ -173,7 +175,7 @@ public void Get_All() var provider = ScopeProvider; using (var scope = provider.CreateScope()) { - var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger()); + var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger(), Mock.Of()); var allEntries = new List(); for (var i = 0; i < 10; i++) @@ -233,7 +235,7 @@ public void Get_All_With_Id() var provider = ScopeProvider; using (var scope = provider.CreateScope()) { - var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger()); + var repo = new PublicAccessRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger(), Mock.Of()); PublicAccessRule[] rules1 = { new PublicAccessRule { RuleValue = "test", RuleType = "RoleName" } }; var entry1 = new PublicAccessEntry(content[0], content[1], content[2], rules1); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RedirectUrlRepositoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RedirectUrlRepositoryTests.cs index f1cf2548a71e..e2182233eccb 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RedirectUrlRepositoryTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RedirectUrlRepositoryTests.cs @@ -3,7 +3,9 @@ using System.Linq; using Microsoft.Extensions.Logging; +using Moq; using NUnit.Framework; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Services; @@ -235,7 +237,7 @@ public void CanSaveAndDelete() } private IRedirectUrlRepository CreateRepository(IScopeProvider provider) => - new RedirectUrlRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger()); + new RedirectUrlRepository((IScopeAccessor)provider, AppCaches, LoggerFactory.CreateLogger(), Mock.Of()); private IContent _textpage; private IContent _subpage; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs index 002c7a463b31..1dc7568bfa8b 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationRepositoryTest.cs @@ -574,9 +574,9 @@ public void CreateTestData() using (var scope = ScopeProvider.CreateScope()) { var accessor = (IScopeAccessor)ScopeProvider; - var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Mock.Of>()); + var relationTypeRepository = new RelationTypeRepository(accessor, AppCaches.Disabled, Mock.Of>(), Mock.Of()); var entityRepository = new EntityRepository(accessor, AppCaches.Disabled); - var relationRepository = new RelationRepository(accessor, Mock.Of>(), relationTypeRepository, entityRepository); + var relationRepository = new RelationRepository(accessor, Mock.Of>(), relationTypeRepository, entityRepository, Mock.Of()); relationTypeRepository.Save(_relateContent); relationTypeRepository.Save(_relateContentType); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs index 948cd07287d5..859681e95259 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/RelationTypeRepositoryTest.cs @@ -3,6 +3,7 @@ using System.Linq; using Microsoft.Extensions.Logging; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; @@ -23,7 +24,7 @@ internal sealed class RelationTypeRepositoryTest : UmbracoIntegrationTest public void SetUp() => CreateTestData(); private RelationTypeRepository CreateRepository(ICoreScopeProvider provider) => - new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger()); + new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), Mock.Of()); [Test] public void Can_Perform_Add_On_RelationTypeRepository() @@ -240,7 +241,7 @@ public void CreateTestData() ICoreScopeProvider provider = ScopeProvider; using (var scope = provider.CreateCoreScope()) { - var repository = new RelationTypeRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger()); + var repository = new RelationTypeRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), Mock.Of()); repository.Save(relateContent); // Id 2 repository.Save(relateContentType); // Id 3 diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ServerRegistrationRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ServerRegistrationRepositoryTest.cs index 2e23b703ca09..c0830b01cfd9 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ServerRegistrationRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ServerRegistrationRepositoryTest.cs @@ -4,6 +4,7 @@ using System.Data.Common; using System.Linq; using Microsoft.Extensions.Logging; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; @@ -28,7 +29,7 @@ public void SetUp() private AppCaches _appCaches; private ServerRegistrationRepository CreateRepository(IScopeProvider provider) => - new((IScopeAccessor)provider, LoggerFactory.CreateLogger()); + new((IScopeAccessor)provider, LoggerFactory.CreateLogger(), Mock.Of()); [Test] public void Cannot_Add_Duplicate_Server_Identities() diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TagRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TagRepositoryTest.cs index 970850449042..bb837b9ad5a7 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TagRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TagRepositoryTest.cs @@ -2,6 +2,7 @@ // See LICENSE for more details. using Microsoft.Extensions.Logging; +using Moq; using NUnit.Framework; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; @@ -1140,5 +1141,5 @@ public void Can_Create_Tag_Relations_With_Mixed_Casing_For_Group() } private TagRepository CreateRepository(IScopeProvider provider) => - new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger()); + new((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), Mock.Of()); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs index 973ea658dc98..46083a97194b 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs @@ -63,7 +63,7 @@ public void TearDown() private IOptionsMonitor RuntimeSettings => GetRequiredService>(); private ITemplateRepository CreateRepository(IScopeProvider provider) => - new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, ViewHelper, RuntimeSettings); + new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, ViewHelper, RuntimeSettings, Mock.Of()); [Test] public void Can_Instantiate_Repository() @@ -262,14 +262,14 @@ public void Can_Perform_Delete_When_Assigned_To_Doc() var templateRepository = CreateRepository(provider); var globalSettings = new GlobalSettings(); var serializer = new SystemTextJsonSerializer(); - var tagRepository = new TagRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger()); + var tagRepository = new TagRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger(), Mock.Of()); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, AppCaches, ShortStringHelper); - var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger()); - var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper); - var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger()); + var languageRepository = new LanguageRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger(), Mock.Of()); + var contentTypeRepository = new ContentTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger(), commonRepository, languageRepository, ShortStringHelper, Mock.Of()); + var relationTypeRepository = new RelationTypeRepository(scopeAccessor, AppCaches.Disabled, LoggerFactory.CreateLogger(), Mock.Of()); var entityRepository = new EntityRepository(scopeAccessor, AppCaches.Disabled); - var relationRepository = new RelationRepository(scopeAccessor, LoggerFactory.CreateLogger(), relationTypeRepository, entityRepository); + var relationRepository = new RelationRepository(scopeAccessor, LoggerFactory.CreateLogger(), relationTypeRepository, entityRepository, Mock.Of()); var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(() => Enumerable.Empty())); var dataValueReferences = diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/UserRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/UserRepositoryTest.cs index 96575a67e731..c5fc76f1480b 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/UserRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/UserRepositoryTest.cs @@ -58,7 +58,8 @@ private UserRepository CreateRepository(ICoreScopeProvider provider) new SystemTextJsonSerializer(), mockRuntimeState.Object, PermissionMappers, - AppPolicyCache); + AppPolicyCache, + Mock.Of()); return repository; } @@ -166,7 +167,8 @@ public void Can_Perform_Delete_On_UserRepository() new SystemTextJsonSerializer(), mockRuntimeState.Object, PermissionMappers, - AppPolicyCache); + AppPolicyCache, + Mock.Of()); repository2.Delete(user); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RedirectUrlServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RedirectUrlServiceTests.cs index 4fa34ee982c5..6996e2c2c50e 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RedirectUrlServiceTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/RedirectUrlServiceTests.cs @@ -38,8 +38,11 @@ public override void CreateTestData() using (var scope = ScopeProvider.CreateScope()) { - var repository = new RedirectUrlRepository((IScopeAccessor)ScopeProvider, AppCaches.Disabled, - Mock.Of>()); + var repository = new RedirectUrlRepository( + (IScopeAccessor)ScopeProvider, + AppCaches.Disabled, + Mock.Of>(), + Mock.Of()); var rootContent = ContentService.GetRootContent().First(); var subPages = ContentService.GetPagedChildren(rootContent.Id, 0, 3, out _).ToList(); _firstSubPage = subPages[0]; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/SingleItemsOnlyCachePolicyTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/SingleItemsOnlyCachePolicyTests.cs index 67c86a3af896..61eb48167024 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/SingleItemsOnlyCachePolicyTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/SingleItemsOnlyCachePolicyTests.cs @@ -36,7 +36,7 @@ public void Get_All_Doesnt_Cache() .Callback((string cacheKey, Func o, TimeSpan? t, bool b) => cached.Add(cacheKey)); cache.Setup(x => x.SearchByKey(It.IsAny())).Returns(new AuditItem[] { }); - var defaultPolicy = new SingleItemsOnlyRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); + var defaultPolicy = new SingleItemsOnlyRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions(), Mock.Of()); var unused = defaultPolicy.GetAll( new object[] { }, @@ -57,7 +57,7 @@ public void Caches_Single() cache.Setup(x => x.Insert(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny())) .Callback(() => isCached = true); - var defaultPolicy = new SingleItemsOnlyRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions()); + var defaultPolicy = new SingleItemsOnlyRepositoryCachePolicy(cache.Object, DefaultAccessor, new RepositoryCachePolicyOptions(), Mock.Of()); var unused = defaultPolicy.Get(1, id => new AuditItem(1, AuditType.Copy, 123, "test", "blah"), ids => null); Assert.IsTrue(isCached); From 905a64d846f4fb2523a37e3b0c61b5d6f32b6cf7 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Tue, 15 Jul 2025 11:51:00 +0200 Subject: [PATCH 08/14] Add checks to specific cache policies --- .../Cache/DefaultRepositoryCachePolicy.cs | 12 ++++++++++++ .../Cache/FullDataSetRepositoryCachePolicy.cs | 16 ++++++++++++++++ .../Cache/MemberRepositoryUsernameCachePolicy.cs | 5 +++++ .../Cache/RepositoryCachePolicyBase.cs | 2 +- 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs index dac79133036d..b3de78e3d650 100644 --- a/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Infrastructure/Cache/DefaultRepositoryCachePolicy.cs @@ -117,6 +117,10 @@ public override void Update(TEntity entity, Action persistUpdated) throw; } + + // We've changed the entity, register cache change for other servers. + // We assume that if something goes wrong, we'll roll back, so don't need to register the change. + RegisterCacheChange(); } /// @@ -141,11 +145,16 @@ public override void Delete(TEntity entity, Action persistDeleted) // if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared Cache.Clear(EntityTypeCacheKey); } + + // We've removed an entity, register cache change for other servers. + RegisterCacheChange(); } /// public override TEntity? Get(TId? id, Func performGet, Func?> performGetAll) { + EnsureCacheIsSynced(); + var cacheKey = GetEntityCacheKey(id); TEntity? fromCache = Cache.GetCacheItem(cacheKey); @@ -182,6 +191,7 @@ public override void Delete(TEntity entity, Action persistDeleted) /// public override TEntity? GetCached(TId id) { + EnsureCacheIsSynced(); var cacheKey = GetEntityCacheKey(id); return Cache.GetCacheItem(cacheKey); } @@ -189,6 +199,7 @@ public override void Delete(TEntity entity, Action persistDeleted) /// public override bool Exists(TId id, Func performExists, Func?> performGetAll) { + EnsureCacheIsSynced(); // if found in cache the return else check var cacheKey = GetEntityCacheKey(id); TEntity? fromCache = Cache.GetCacheItem(cacheKey); @@ -198,6 +209,7 @@ public override bool Exists(TId id, Func performExists, Func public override TEntity[] GetAll(TId[]? ids, Func?> performGetAll) { + EnsureCacheIsSynced(); if (ids?.Length > 0) { // try to get each entity from the cache diff --git a/src/Umbraco.Infrastructure/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Infrastructure/Cache/FullDataSetRepositoryCachePolicy.cs index 91ae85c84f68..829f8dc44c25 100644 --- a/src/Umbraco.Infrastructure/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Infrastructure/Cache/FullDataSetRepositoryCachePolicy.cs @@ -100,6 +100,10 @@ public override void Update(TEntity entity, Action persistUpdated) { ClearAll(); } + + // We've changed the entity, register cache change for other servers. + // We assume that if something goes wrong, we'll roll back, so don't need to register the change. + RegisterCacheChange(); } /// @@ -118,11 +122,17 @@ public override void Delete(TEntity entity, Action persistDeleted) { ClearAll(); } + + // We've changed the entity, register cache change for other servers. + // We assume that if something goes wrong, we'll roll back, so don't need to register the change. + RegisterCacheChange(); } /// public override TEntity? Get(TId? id, Func performGet, Func?> performGetAll) { + EnsureCacheIsSynced(); + // get all from the cache, then look for the entity IEnumerable all = GetAllCached(performGetAll); TEntity? entity = all.FirstOrDefault(x => _entityGetId(x)?.Equals(id) ?? false); @@ -135,6 +145,8 @@ public override void Delete(TEntity entity, Action persistDeleted) /// public override TEntity? GetCached(TId id) { + EnsureCacheIsSynced(); + // get all from the cache -- and only the cache, then look for the entity DeepCloneableList? all = Cache.GetCacheItem>(GetEntityTypeCacheKey()); TEntity? entity = all?.FirstOrDefault(x => _entityGetId(x)?.Equals(id) ?? false); @@ -147,6 +159,8 @@ public override void Delete(TEntity entity, Action persistDeleted) /// public override bool Exists(TId id, Func performExits, Func?> performGetAll) { + EnsureCacheIsSynced(); + // get all as one set, then look for the entity IEnumerable all = GetAllCached(performGetAll); return all.Any(x => _entityGetId(x)?.Equals(id) ?? false); @@ -155,6 +169,8 @@ public override bool Exists(TId id, Func performExits, Func public override TEntity[] GetAll(TId[]? ids, Func?> performGetAll) { + EnsureCacheIsSynced(); + // get all as one set, from cache if possible, else repo IEnumerable all = GetAllCached(performGetAll); diff --git a/src/Umbraco.Infrastructure/Cache/MemberRepositoryUsernameCachePolicy.cs b/src/Umbraco.Infrastructure/Cache/MemberRepositoryUsernameCachePolicy.cs index 0340e492c624..00f57f0eb117 100644 --- a/src/Umbraco.Infrastructure/Cache/MemberRepositoryUsernameCachePolicy.cs +++ b/src/Umbraco.Infrastructure/Cache/MemberRepositoryUsernameCachePolicy.cs @@ -31,6 +31,8 @@ public MemberRepositoryUsernameCachePolicy( public IMember? GetByUserName(string key, string? username, Func performGetByUsername, Func?> performGetAll) { + EnsureCacheIsSynced(); + var cacheKey = GetEntityCacheKey(key + username); IMember? fromCache = Cache.GetCacheItem(cacheKey); @@ -52,6 +54,9 @@ public MemberRepositoryUsernameCachePolicy( public void DeleteByUserName(string key, string? username) { + // We've removed an entity, register cache change for other servers. + RegisterCacheChange(); + var cacheKey = GetEntityCacheKey(key + username); Cache.ClearByKey(cacheKey); } diff --git a/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs b/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs index 510265c0aec6..3df02c800026 100644 --- a/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs +++ b/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs @@ -88,7 +88,7 @@ protected IAppPolicyCache Cache /// /// Ensures that the cache is synced with the database. /// - protected void EsnureCacheIsSynced() + protected void EnsureCacheIsSynced() { var synced = _cacheVersionService.IsCacheSyncedAsync().GetAwaiter().GetResult(); if (synced is false) From 0d15c75b6df9a58449277482428e5f0ac72bfb8d Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Tue, 15 Jul 2025 13:02:49 +0200 Subject: [PATCH 09/14] Fix migration --- .../Migrations/Upgrade/UmbracoPlan.cs | 3 --- .../Migrations/Upgrade/UmbracoPremigrationPlan.cs | 6 ++++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs index fd49eeda84dc..21cfe558a688 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs @@ -119,8 +119,5 @@ protected virtual void DefinePlan() // To 16.0.0 To("{C6681435-584F-4BC8-BB8D-BC853966AF0B}"); To("{D1568C33-A697-455F-8D16-48060CB954A1}"); - - // TO 16.2.0 - To("{A1B3F5D6-4C8B-4E7A-9F8C-1D2B3E4F5A6B}"); } } diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs index d9774aa1ea7a..177d7333fc94 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPremigrationPlan.cs @@ -67,5 +67,11 @@ protected virtual void DefinePlan() To("{B9133686-B758-404D-AF12-708AA80C7E44}"); To("{EEB1F012-B44D-4AB4-8756-F7FB547345B4}"); To("{0F49E1A4-AFD8-4673-A91B-F64E78C48174}"); + + // TO 16.2.0 + // The lock and table are required to access caches + // When logging in, we save the user to the cache so these needs to have run. + To("{1DC39DC7-A88A-4912-8E60-4FD36246E8D1}"); + To("{A1B3F5D6-4C8B-4E7A-9F8C-1D2B3E4F5A6B}"); } } From e315163aba57d0d1a81e7d49283d665eac945f2a Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Tue, 15 Jul 2025 13:03:12 +0200 Subject: [PATCH 10/14] Add to schema creator --- .../Migrations/Install/DatabaseSchemaCreator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs index cd7fcd44d0bf..30e62dd58280 100644 --- a/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Infrastructure/Migrations/Install/DatabaseSchemaCreator.cs @@ -90,6 +90,7 @@ public class DatabaseSchemaCreator typeof(WebhookLogDto), typeof(WebhookRequestDto), typeof(UserDataDto), + typeof(RepositoryCacheVersionDto), }; private readonly IUmbracoDatabase _database; From bc649132bee7140f7ce3ab3c63bf184867dab50d Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Tue, 15 Jul 2025 15:28:29 +0200 Subject: [PATCH 11/14] Fix database access --- .../Cache/RepositoryCacheVersionService.cs | 4 +--- .../Cache/RepositoryCachePolicyBase.cs | 2 +- .../Persistence/Dtos/RepositoryCacheVersionDto.cs | 4 ++-- .../Implement/RepositoryCacheVersionRepository.cs | 11 ++++------- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs b/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs index e2809963c50e..d97b60b710b0 100644 --- a/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs +++ b/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs @@ -31,7 +31,7 @@ public async Task IsCacheSyncedAsync() _logger.LogDebug("Checking if cache for {EntityType} is synced", typeof(TEntity).Name); // We have to take a read lock to ensure the cache is not being updated while we check the version - using ICoreScope scope = _scopeProvider.CreateCoreScope(); + using ICoreScope scope = _scopeProvider.CreateCoreScope(autoComplete: true); scope.ReadLock(Constants.Locks.CacheVersion); var cacheKey = GetCacheKey(); @@ -44,7 +44,6 @@ public async Task IsCacheSyncedAsync() } RepositoryCacheVersion? databaseVersion = await _repositoryCacheVersionRepository.GetAsync(cacheKey); - scope.Complete(); if (databaseVersion?.Version is null) { @@ -67,7 +66,6 @@ public async Task IsCacheSyncedAsync() _logger.LogDebug("Cache for {EntityType} is synced", typeof(TEntity).Name); return true; - } /// diff --git a/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs b/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs index 3df02c800026..1f4dbf051df0 100644 --- a/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs +++ b/src/Umbraco.Infrastructure/Cache/RepositoryCachePolicyBase.cs @@ -100,5 +100,5 @@ protected void EnsureCacheIsSynced() /// /// Registers a change in the cache. /// - protected void RegisterCacheChange() => _cacheVersionService.SetCacheUpdatedAsync().Wait(); + protected void RegisterCacheChange() => _cacheVersionService.SetCacheUpdatedAsync().GetAwaiter().GetResult(); } diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/RepositoryCacheVersionDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/RepositoryCacheVersionDto.cs index dc227df2a4f3..a06fceed9a5a 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/RepositoryCacheVersionDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/RepositoryCacheVersionDto.cs @@ -5,7 +5,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos; [TableName(TableName)] -[PrimaryKey("cacheIdentifier")] +[PrimaryKey("identifier", AutoIncrement = false)] [ExplicitColumns] public class RepositoryCacheVersionDto { @@ -13,7 +13,7 @@ public class RepositoryCacheVersionDto [Column("identifier")] [Length(256)] - [PrimaryKeyColumn(AutoIncrement = false, Clustered = true)] + [PrimaryKeyColumn(Name = "PK_umbracoRepositoryCacheVersion", AutoIncrement = false, Clustered = true)] public required string Identifier { get; set; } [Column("version")] diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryCacheVersionRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryCacheVersionRepository.cs index d64f31afdc31..334b51432672 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryCacheVersionRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryCacheVersionRepository.cs @@ -25,13 +25,13 @@ public RepositoryCacheVersionRepository(IScopeAccessor scopeAccessor, AppCaches } Sql query = Sql() - .Select() + .Select(x => x.Version) .From() .Where(x => x.Identifier == identifier); - RepositoryCacheVersionDto? dto = (await Database.FetchAsync(query)).FirstOrDefault(); + var version = await Database.ExecuteScalarAsync(query); - return Map(dto); + return new RepositoryCacheVersion { Identifier = identifier, Version = version }; } public async Task> GetAllAsync() @@ -48,10 +48,7 @@ public async Task> GetAllAsync() public async Task SaveAsync(RepositoryCacheVersion repositoryCacheVersion) { RepositoryCacheVersionDto dto = Map(repositoryCacheVersion); - await Database.InsertOrUpdateAsync( - dto, - "SET identifier = @identifier, version = @version", - new { identifier = dto.Identifier, version = dto.Version, }); + await Database.InsertOrUpdateAsync(dto, null, null); } private static RepositoryCacheVersionDto Map(RepositoryCacheVersion entity) From 6ec47fdf6893f71f264917cc7e9dbc2dbd9c4127 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Tue, 15 Jul 2025 15:30:28 +0200 Subject: [PATCH 12/14] Initialize the cache version on in memory miss --- .../Cache/RepositoryCacheVersionService.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs b/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs index d97b60b710b0..a886efd132e7 100644 --- a/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs +++ b/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs @@ -35,13 +35,6 @@ public async Task IsCacheSyncedAsync() scope.ReadLock(Constants.Locks.CacheVersion); var cacheKey = GetCacheKey(); - if (_cacheVersions.TryGetValue(cacheKey, out Guid localVersion) is false) - { - _logger.LogDebug("Cache for {EntityType} is not initialized, considering it synced", typeof(TEntity).Name); - - // We're not initialized yet, so cache is empty, which means cache is synced. - return true; - } RepositoryCacheVersion? databaseVersion = await _repositoryCacheVersionRepository.GetAsync(cacheKey); @@ -53,6 +46,16 @@ public async Task IsCacheSyncedAsync() return true; } + if (_cacheVersions.TryGetValue(cacheKey, out Guid localVersion) is false) + { + _logger.LogDebug("Cache for {EntityType} is not initialized, considering it synced", typeof(TEntity).Name); + + // We're not initialized yet, so cache is empty, which means cache is synced. + // Since the cache is most likely no longer empty, we should set the cache version to the database version. + _cacheVersions[cacheKey] = Guid.Parse(databaseVersion.Version); + return true; + } + // We could've parsed this in the repository layer; however, the fact that we are using a Guid is an implementation detail. if (localVersion != Guid.Parse(databaseVersion.Version)) { From 7e79947d5c24873480aac7a51ed131bf39945aab Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Wed, 16 Jul 2025 15:58:30 +0200 Subject: [PATCH 13/14] Make cache version service internal --- src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs b/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs index a886efd132e7..ea822eed0999 100644 --- a/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs +++ b/src/Umbraco.Core/Cache/RepositoryCacheVersionService.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Core.Cache; /// -public class RepositoryCacheVersionService : IRepositoryCacheVersionService +internal class RepositoryCacheVersionService : IRepositoryCacheVersionService { private readonly ICoreScopeProvider _scopeProvider; private readonly IRepositoryCacheVersionRepository _repositoryCacheVersionRepository; @@ -112,7 +112,7 @@ public async Task SetCachesSyncedAsync() scope.Complete(); } - private string GetCacheKey() + internal string GetCacheKey() where TEntity : class => typeof(TEntity).Name; } From f976e52271579728fbd5e3706bc57be9feecb082 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Wed, 16 Jul 2025 15:58:36 +0200 Subject: [PATCH 14/14] Add tests --- .../RepositoryCacheVersionServiceTests.cs | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.Core/Services/RepositoryCacheVersionServiceTests.cs diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/RepositoryCacheVersionServiceTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/RepositoryCacheVersionServiceTests.cs new file mode 100644 index 000000000000..28c6e6ae2f67 --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.Core/Services/RepositoryCacheVersionServiceTests.cs @@ -0,0 +1,106 @@ +using NUnit.Framework; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.Core.Services; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)] +internal sealed class RepositoryCacheVersionServiceTests : UmbracoIntegrationTest +{ + private IRepositoryCacheVersionService RepositoryCacheVersionService => GetRequiredService(); + + private IRepositoryCacheVersionRepository RepositoryCacheVersionRepository => GetRequiredService(); + + private ICoreScopeProvider CoreScopeProvider => GetRequiredService(); + + [Test] + public async Task Cache_Is_Initially_Synced() + { + var isSynced = await RepositoryCacheVersionService.IsCacheSyncedAsync(); + Assert.IsTrue(isSynced, "Cache should be initially synced."); + } + + [Test] + public async Task SetCacheUpdatedAsync_Writes_Version_To_Database() + { + using var scope = CoreScopeProvider.CreateCoreScope(autoComplete: true); + var initial = await RepositoryCacheVersionRepository.GetAsync(GetCacheKey()); + Assert.IsNull(initial?.Version, "Initial cache version should be null before update."); + + await RepositoryCacheVersionService.SetCacheUpdatedAsync(); + + var cacheVersion = await RepositoryCacheVersionRepository.GetAsync(GetCacheKey()); + Assert.IsNotNull(cacheVersion, "Cache version should exist in the database after update."); + Assert.IsFalse(string.IsNullOrEmpty(cacheVersion.Version), "Cache version string should not be null or empty."); + } + + [Test] + public async Task Cache_Is_Out_Of_Sync_If_Updated_Remotely() + { + // Simulate an update + await RepositoryCacheVersionService.SetCacheUpdatedAsync(); + var isSynced = await RepositoryCacheVersionService.IsCacheSyncedAsync(); + + // We should be synced now. + Assert.IsTrue(isSynced); + + using (var scope = CoreScopeProvider.CreateCoreScope()) + { + // Simulate a remote update to the database + await RepositoryCacheVersionRepository.SaveAsync(GetRepositoryRandomCacheVersion()); + scope.Complete(); + } + + // Now the cache should be out of sync + isSynced = await RepositoryCacheVersionService.IsCacheSyncedAsync(); + Assert.IsFalse(isSynced, "Cache should be out of sync after remote update."); + } + + [Test] + public async Task SetCacheUpdatedAsync_Updates_Cache_Version() + { + using var scope = CoreScopeProvider.CreateCoreScope(autoComplete: true); + + await RepositoryCacheVersionService.SetCacheUpdatedAsync(); + var initialCacheVersion = await RepositoryCacheVersionRepository.GetAsync(GetCacheKey()); + Assert.IsNotNull(initialCacheVersion, "Initial cache version should not be null."); + + await RepositoryCacheVersionService.SetCacheUpdatedAsync(); + var updatedCacheVersion = await RepositoryCacheVersionRepository.GetAsync(GetCacheKey()); + Assert.IsNotNull(updatedCacheVersion, "Updated cache version should not be null."); + + Assert.AreNotEqual(initialCacheVersion.Version, updatedCacheVersion.Version, "Cache version should be updated."); + } + + [Test] + public async Task CacheVersion_Is_Unique_Per_Repository_Type() + { + using var scope = CoreScopeProvider.CreateCoreScope(autoComplete: true); + + await RepositoryCacheVersionService.SetCacheUpdatedAsync(); + await RepositoryCacheVersionService.SetCacheUpdatedAsync(); + + var contentVersion = (await RepositoryCacheVersionRepository.GetAsync(GetCacheKey()))?.Version; + var mediaKey = ((RepositoryCacheVersionService)RepositoryCacheVersionService).GetCacheKey(); + var mediaVersion = (await RepositoryCacheVersionRepository.GetAsync(mediaKey))?.Version; + + Assert.IsNotNull(contentVersion); + Assert.IsNotNull(mediaVersion); + Assert.AreNotEqual(contentVersion, mediaVersion, "Cache versions should be unique for different repository types."); + } + + private RepositoryCacheVersion GetRepositoryRandomCacheVersion() + => new() + { + Identifier = GetCacheKey(), + Version = Guid.NewGuid().ToString(), + }; + + private string GetCacheKey() + => ((RepositoryCacheVersionService)RepositoryCacheVersionService).GetCacheKey(); +}