From 8bbf3d9342b356b02327ee27504dfb80e49ac2c4 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Tue, 24 Mar 2026 14:03:16 +0000 Subject: [PATCH 01/18] Change to a full set cache policity, so we hit the database less times. --- .../SyncMigrateFullDataSetCachePolicy.cs | 172 ++++++++++++++++++ .../SyncMigratedDataBuilderExtensions.cs | 2 +- .../Migrations/SyncMigratedDataRepository.cs | 2 +- .../ISyncDataFullSetCachePolicy.cs | 12 ++ .../Persistance/SyncDataRespositoryBase.cs | 33 +--- .../SyncMigratedDataCachePolicy.cs | 24 ++- 6 files changed, 213 insertions(+), 32 deletions(-) create mode 100644 uSync.Core/Migrations/SyncMigrateFullDataSetCachePolicy.cs create mode 100644 uSync.Core/Persistance/ISyncDataFullSetCachePolicy.cs rename uSync.Core/{Migrations => Persistance}/SyncMigratedDataCachePolicy.cs (51%) diff --git a/uSync.Core/Migrations/SyncMigrateFullDataSetCachePolicy.cs b/uSync.Core/Migrations/SyncMigrateFullDataSetCachePolicy.cs new file mode 100644 index 00000000..e407ab8a --- /dev/null +++ b/uSync.Core/Migrations/SyncMigrateFullDataSetCachePolicy.cs @@ -0,0 +1,172 @@ +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Infrastructure.Scoping; +using Umbraco.Extensions; + +using uSync.Core.Persistance; + +using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope; + +namespace uSync.Core.Migrations; + +/// +/// this is simliar to the SyncDataCachePolicy, except everything is cached in one key, +/// +/// +/// caching all entites, works when it is unlikely they will change much duing the lookup +/// phase, and there are not a lot (e.g 100+s) of entrires, we can cache them, and then +/// all the lookups don't hit the database. +/// +internal class SyncMigrateFullDataSetCachePolicy + : ISyncDataFullSetCachePolicy + where TModel : class, ISyncDataEntity +{ + private readonly IAppPolicyCache _globalCache; + private readonly IScopeAccessor _scopeAccessor; + private readonly IRepositoryCacheVersionService _repositoryCacheVersionService; + private readonly ICacheSyncService _cacheSyncService; + + public SyncMigrateFullDataSetCachePolicy( + IAppPolicyCache globalCache, + IScopeAccessor scopeAccessor, + IRepositoryCacheVersionService repositoryCacheVersionService, + ICacheSyncService cacheSyncService) + { + _globalCache = globalCache; + _scopeAccessor = scopeAccessor; + _repositoryCacheVersionService = repositoryCacheVersionService; + _cacheSyncService = cacheSyncService; + } + + private IAppPolicyCache Cache + { + get + { + IScope? ambientScope = _scopeAccessor.AmbientScope; + return (ambientScope?.RepositoryCacheMode) switch + { + RepositoryCacheMode.Default => _globalCache, + RepositoryCacheMode.Scoped => ambientScope.IsolatedCaches.GetOrCreate(), + RepositoryCacheMode.None => NoAppCache.Instance, + _ => throw new NotSupportedException($"RepositoryCacheMode {ambientScope?.RepositoryCacheMode} is not supported"), + }; + } + } + private string _dataSetCacheKey => $"{typeof(TModel).FullName}_FullDataSet"; + + public void ClearAllAsync() + => Cache.ClearByKey(_dataSetCacheKey); + + public async Task CreateAsync(TModel model, Func persistNewAsync, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(model); + try + { + await persistNewAsync(model); + } + finally + { + ClearAllAsync(); + } + } + + public Task DeleteAsync(TModel model, Func persistDeleteAsync, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(model); + try + { + return persistDeleteAsync(model); + } + finally + { + ClearAllAsync(); + } + } + + public Task UpdateAsync(TModel model, Func persistUpdateAsync, CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(model); + try + { + return persistUpdateAsync(model); + } + finally + { + ClearAllAsync(); + } + } + + public async Task ExistsAsync(TKey key, Func>> performGetAllAsync, CancellationToken cancellationToken = default) + { + await EnsureCacheIsSyncedAsync(); + + var all = await GetAllCached(performGetAllAsync, cancellationToken); + return all.Any(x => x.Key?.Equals(key) is true); + } + + public async Task GetAllAsync(TKey[]? keys, Func>> performGetAllAsync, CancellationToken cancellationToken = default) + { + await EnsureCacheIsSyncedAsync(); + + var all = await GetAllCached(performGetAllAsync, cancellationToken); + if (keys?.Length > 0) + { + return [.. all.Where(x => keys.Contains(x.Key))]; + } + else + { + return all; + } + } + + public async Task GetAsync(TKey key, Func>> performGetAllAsync, CancellationToken cancellationToken = default) + { + await EnsureCacheIsSyncedAsync(); + + var all = await GetAllCached(performGetAllAsync, cancellationToken); + return all.FirstOrDefault(x => x.Key?.Equals(key) is true); + } + + SemaphoreSlim _semaphoreLock = new SemaphoreSlim(1); + + private async Task GetAllCached(Func>> performGetAllAsync, CancellationToken cancellationToken) + { + var all = Cache.GetCacheItem(_dataSetCacheKey); + if (all is not null) return all; + + try + { + await _semaphoreLock.WaitAsync(cancellationToken); + + // try in the lock, possible something else filled it in while we waited. + all = Cache.GetCacheItem(_dataSetCacheKey); + if (all is not null) return all; + + // go get the data from the database. + TModel[] entries = [.. (await performGetAllAsync())]; + await InsertCacheEntries(entries); + return entries; + } + finally + { + _semaphoreLock.Release(); + } + } + + private Task InsertCacheEntries(TModel[] entries) + { + Cache.Insert(_dataSetCacheKey, () => entries, TimeSpan.FromMinutes(10), true); + return Task.CompletedTask; + } + + + private async Task EnsureCacheIsSyncedAsync() + { + var synced = await _repositoryCacheVersionService.IsCacheSyncedAsync(); + if (synced) return; + + _cacheSyncService.SyncInternal(CancellationToken.None); + } + + +} diff --git a/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs b/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs index af31ff21..2e108e50 100644 --- a/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs +++ b/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs @@ -18,7 +18,7 @@ internal static class SyncMigratedDataBuilderExtensions { public static IUmbracoBuilder AddSyncMigratedData(this IUmbracoBuilder builder) { - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.AddNotificationAsyncHandler(); diff --git a/uSync.Core/Migrations/SyncMigratedDataRepository.cs b/uSync.Core/Migrations/SyncMigratedDataRepository.cs index 337f6cc1..bdde1ae9 100644 --- a/uSync.Core/Migrations/SyncMigratedDataRepository.cs +++ b/uSync.Core/Migrations/SyncMigratedDataRepository.cs @@ -12,7 +12,7 @@ internal class SyncMigratedDataRepository public SyncMigratedDataRepository( IScopeAccessor scopeAccessor, AppCaches appCaches, - ISyncMigratedDataCachePolicy cachePolicy) + ISyncMigratedFullDataSetCachePolicy cachePolicy) : base(scopeAccessor, appCaches, cachePolicy, SyncMigrations.MigratedDataTableName) { } diff --git a/uSync.Core/Persistance/ISyncDataFullSetCachePolicy.cs b/uSync.Core/Persistance/ISyncDataFullSetCachePolicy.cs new file mode 100644 index 00000000..aba51edd --- /dev/null +++ b/uSync.Core/Persistance/ISyncDataFullSetCachePolicy.cs @@ -0,0 +1,12 @@ +namespace uSync.Core.Persistance; + +public interface ISyncDataFullSetCachePolicy where TModel : class, ISyncDataEntity +{ + void ClearAllAsync(); + Task CreateAsync(TModel model, Func persistNewAsync, CancellationToken cancellationToken = default); + Task DeleteAsync(TModel model, Func persistDeleteAsync, CancellationToken cancellationToken = default); + Task ExistsAsync(TKey key, Func>> performGetAllAsync, CancellationToken cancellationToken = default); + Task GetAllAsync(TKey[]? keys, Func>> performGetAllAsync, CancellationToken cancellationToken = default); + Task GetAsync(TKey key, Func>> performGetAllAsync, CancellationToken cancellationToken = default); + Task UpdateAsync(TModel model, Func persistUpdateAsync, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/uSync.Core/Persistance/SyncDataRespositoryBase.cs b/uSync.Core/Persistance/SyncDataRespositoryBase.cs index de239cc7..5cce3e8c 100644 --- a/uSync.Core/Persistance/SyncDataRespositoryBase.cs +++ b/uSync.Core/Persistance/SyncDataRespositoryBase.cs @@ -11,7 +11,8 @@ namespace uSync.Core.Persistance; internal abstract class SyncDataRespositoryBase : ISyncDataRespository where TModel : class, ISyncDataEntity { - protected readonly ISyncDataRepositoryCachePolicy _cachePolicy; + // protected readonly ISyncDataRepositoryCachePolicy _cachePolicy; + protected readonly ISyncDataFullSetCachePolicy _cachePolicy; protected readonly IScopeAccessor _scopeAccessor; protected readonly AppCaches _appCaches; @@ -20,7 +21,7 @@ internal abstract class SyncDataRespositoryBase : ISyncDataResposit public SyncDataRespositoryBase( IScopeAccessor scopeAccessor, AppCaches appCaches, - ISyncDataRepositoryCachePolicy cachePolicy, + ISyncDataFullSetCachePolicy cachePolicy, string tableName) { _scopeAccessor = scopeAccessor; @@ -71,10 +72,10 @@ public virtual async Task DeleteAsync(TModel item) => await _cachePolicy.DeleteAsync(item, PersistDeletedItemAsync); public virtual async Task ExistsAsync(Key key) - => await _cachePolicy.ExistsAsync(key, PerformExistsAsync); + => await _cachePolicy.ExistsAsync(key, PerformGetAllAsync); public virtual async Task GetAsync(Key key) - => await _cachePolicy.GetAsync(key, PerformGetAsync); + => await _cachePolicy.GetAsync(key, PerformGetAllAsync); public virtual async Task> GetAllAsync(params Key[] keys) => await _cachePolicy.GetAllAsync(keys, PerformGetAllAsync); @@ -112,31 +113,9 @@ private async Task PersistDeletedItemAsync(TModel model) } } - private async Task PerformExistsAsync(Key key) - { - var sql = GetBaseQuery(true) - .Where(GetBaseWhereClause(), new { Key = key }); - return await Database.ExecuteScalarAsync(sql) > 0; - } - - private async Task PerformGetAsync(Key id) - { - var sql = GetBaseQuery(false) - .Where(GetBaseWhereClause(), new { Key = id }); - - return await Database.FirstOrDefaultAsync(sql); - } - - private async Task> PerformGetAllAsync(Key[]? keys) + private async Task> PerformGetAllAsync() { var sql = GetBaseQuery(false); - - if (keys is null || keys.Length == 0) - return await Database.FetchAsync(sql); - - var uniqueIds = keys.Distinct().ToArray(); - sql.Where($"{SqlSyntax.GetQuotedColumnName("Key")} IN (@Keys)", new { Keys = uniqueIds }); - return await Database.FetchAsync(sql); } diff --git a/uSync.Core/Migrations/SyncMigratedDataCachePolicy.cs b/uSync.Core/Persistance/SyncMigratedDataCachePolicy.cs similarity index 51% rename from uSync.Core/Migrations/SyncMigratedDataCachePolicy.cs rename to uSync.Core/Persistance/SyncMigratedDataCachePolicy.cs index 54738ea4..44a363ca 100644 --- a/uSync.Core/Migrations/SyncMigratedDataCachePolicy.cs +++ b/uSync.Core/Persistance/SyncMigratedDataCachePolicy.cs @@ -5,9 +5,9 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Infrastructure.Scoping; -using uSync.Core.Persistance; +using uSync.Core.Migrations; -namespace uSync.Core.Migrations; +namespace uSync.Core.Persistance; internal class SyncMigratedDataCachePolicy : SyncDataRepositoryCachePolicy , ISyncMigratedDataCachePolicy @@ -23,4 +23,22 @@ public SyncMigratedDataCachePolicy( public interface ISyncMigratedDataCachePolicy : ISyncDataRepositoryCachePolicy -{ } \ No newline at end of file +{ } + +internal class SyncMigratedFullDataSetCachePolicy : SyncMigrateFullDataSetCachePolicy + , ISyncMigratedFullDataSetCachePolicy +{ + public SyncMigratedFullDataSetCachePolicy( + IAppPolicyCache globalCache, + IScopeAccessor scopeAccessor, + IRepositoryCacheVersionService repositoryCacheVersionService, + ICacheSyncService cacheSyncService) + : base(globalCache, scopeAccessor, repositoryCacheVersionService, cacheSyncService) + { } +} + +public interface ISyncMigratedFullDataSetCachePolicy + : ISyncDataFullSetCachePolicy +{ + +} \ No newline at end of file From 53672a1000cf7e2dc8b24f4ba011ee487f9cda1c Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Tue, 24 Mar 2026 14:17:39 +0000 Subject: [PATCH 02/18] update the repo to catch errors (so we don't bork an import/site) --- .../Migrations/SyncMigratedDataRepository.cs | 9 +- .../Persistance/SyncDataRespositoryBase.cs | 82 +++++++++++++++---- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/uSync.Core/Migrations/SyncMigratedDataRepository.cs b/uSync.Core/Migrations/SyncMigratedDataRepository.cs index bdde1ae9..09d77524 100644 --- a/uSync.Core/Migrations/SyncMigratedDataRepository.cs +++ b/uSync.Core/Migrations/SyncMigratedDataRepository.cs @@ -1,4 +1,6 @@ -using Umbraco.Cms.Core.Cache; +using Microsoft.Extensions.Logging; + +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Infrastructure.Scoping; using uSync.Core.Persistance; @@ -12,8 +14,9 @@ internal class SyncMigratedDataRepository public SyncMigratedDataRepository( IScopeAccessor scopeAccessor, AppCaches appCaches, - ISyncMigratedFullDataSetCachePolicy cachePolicy) + ISyncMigratedFullDataSetCachePolicy cachePolicy, + ILogger logger) : base(scopeAccessor, appCaches, cachePolicy, - SyncMigrations.MigratedDataTableName) + SyncMigrations.MigratedDataTableName, logger) { } } diff --git a/uSync.Core/Persistance/SyncDataRespositoryBase.cs b/uSync.Core/Persistance/SyncDataRespositoryBase.cs index 5cce3e8c..ee6d1747 100644 --- a/uSync.Core/Persistance/SyncDataRespositoryBase.cs +++ b/uSync.Core/Persistance/SyncDataRespositoryBase.cs @@ -1,4 +1,6 @@ -using NPoco; +using Microsoft.Extensions.Logging; + +using NPoco; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Infrastructure.Persistence; @@ -8,13 +10,22 @@ namespace uSync.Core.Persistance; +/// +/// repository to store some migration info (for when types change from one type to another). +/// +/// +/// we are super causious with this data, it's not the end of the world if it's not here (for most sites). +/// and this is the only SQL that uSync does anywhere, so we are guarding it, so if it failes we carry on +/// and log the errors so people can see them. +/// + internal abstract class SyncDataRespositoryBase : ISyncDataRespository where TModel : class, ISyncDataEntity { - // protected readonly ISyncDataRepositoryCachePolicy _cachePolicy; protected readonly ISyncDataFullSetCachePolicy _cachePolicy; protected readonly IScopeAccessor _scopeAccessor; protected readonly AppCaches _appCaches; + protected readonly ILogger> _logger; protected readonly string _tableName; @@ -22,13 +33,15 @@ public SyncDataRespositoryBase( IScopeAccessor scopeAccessor, AppCaches appCaches, ISyncDataFullSetCachePolicy cachePolicy, - string tableName) + string tableName, + ILogger> logger) { _scopeAccessor = scopeAccessor; _appCaches = appCaches; _cachePolicy = cachePolicy; _tableName = tableName; + _logger = logger; } protected IScope AmbientScope @@ -82,41 +95,74 @@ public virtual async Task> GetAllAsync(params Key[] keys) private async Task PersistNewItemAsync(TModel model) { - if (await ExistsAsync(model.Key)) - throw new InvalidOperationException($"An item with the id {model.Key} already exists."); + try + { + if (await ExistsAsync(model.Key)) + throw new InvalidOperationException($"An item with the id {model.Key} already exists."); - using (var transaction = Database.GetTransaction()) + using (var transaction = Database.GetTransaction()) + { + await Database.InsertAsync(model); + transaction.Complete(); + } + } + catch(Exception ex) { - await Database.InsertAsync(model); - transaction.Complete(); + _logger.LogWarning(ex, "uSync Migration - Persist New Item Failed"); + return; } } private async Task PersistUpdatedItemAsync(TModel model) { - if (await ExistsAsync(model.Key) == false) - throw new InvalidOperationException($"An item with the key {model.Key} does not exist."); + try + { + if (await ExistsAsync(model.Key) == false) + throw new InvalidOperationException($"An item with the key {model.Key} does not exist."); - using (var transaction = Database.GetTransaction()) + using (var transaction = Database.GetTransaction()) + { + await Database.UpdateAsync(model); + transaction.Complete(); + } + } + catch(Exception ex) { - await Database.UpdateAsync(model); - transaction.Complete(); + _logger.LogWarning(ex, "uSync Migration Update Query Failed"); + return; } } private async Task PersistDeletedItemAsync(TModel model) { - var deletes = GetDeleteClauses(); - foreach (var delete in deletes) + try + { + var deletes = GetDeleteClauses(); + foreach (var delete in deletes) + { + await Database.ExecuteAsync(delete, new { model.Key }); + } + } + catch(Exception ex) { - await Database.ExecuteAsync(delete, new { model.Key }); + _logger.LogWarning(ex, "uSync Migration Delete Failed"); + return; } } private async Task> PerformGetAllAsync() { - var sql = GetBaseQuery(false); - return await Database.FetchAsync(sql); + try + { + var sql = GetBaseQuery(false); + return await Database.FetchAsync(sql); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "uSync Migration Query Failed"); + return []; + } + } } \ No newline at end of file From a9ea9e3487480e01a87d2a4d65a91b43358c2448 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Tue, 24 Mar 2026 15:30:18 +0000 Subject: [PATCH 03/18] Update uSync.Core/Persistance/SyncDataRespositoryBase.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- uSync.Core/Persistance/SyncDataRespositoryBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uSync.Core/Persistance/SyncDataRespositoryBase.cs b/uSync.Core/Persistance/SyncDataRespositoryBase.cs index ee6d1747..3dee801e 100644 --- a/uSync.Core/Persistance/SyncDataRespositoryBase.cs +++ b/uSync.Core/Persistance/SyncDataRespositoryBase.cs @@ -14,7 +14,7 @@ namespace uSync.Core.Persistance; /// repository to store some migration info (for when types change from one type to another). /// /// -/// we are super causious with this data, it's not the end of the world if it's not here (for most sites). +/// we are super cautious with this data, it's not the end of the world if it's not here (for most sites). /// and this is the only SQL that uSync does anywhere, so we are guarding it, so if it failes we carry on /// and log the errors so people can see them. /// From af5f1cb88f203d0f0890aef7ea0de1f8234f6d98 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Tue, 24 Mar 2026 15:30:29 +0000 Subject: [PATCH 04/18] Update uSync.Core/Persistance/SyncDataRespositoryBase.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- uSync.Core/Persistance/SyncDataRespositoryBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uSync.Core/Persistance/SyncDataRespositoryBase.cs b/uSync.Core/Persistance/SyncDataRespositoryBase.cs index 3dee801e..2e8954c8 100644 --- a/uSync.Core/Persistance/SyncDataRespositoryBase.cs +++ b/uSync.Core/Persistance/SyncDataRespositoryBase.cs @@ -15,7 +15,7 @@ namespace uSync.Core.Persistance; /// /// /// we are super cautious with this data, it's not the end of the world if it's not here (for most sites). -/// and this is the only SQL that uSync does anywhere, so we are guarding it, so if it failes we carry on +/// and this is the only SQL that uSync does anywhere, so we are guarding it, so if it fails we carry on /// and log the errors so people can see them. /// From d08f6c8a57f2d9ada658e02bf14ed54086858dc1 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Tue, 24 Mar 2026 15:30:40 +0000 Subject: [PATCH 05/18] Update uSync.Core/Migrations/SyncMigrateFullDataSetCachePolicy.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- uSync.Core/Migrations/SyncMigrateFullDataSetCachePolicy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uSync.Core/Migrations/SyncMigrateFullDataSetCachePolicy.cs b/uSync.Core/Migrations/SyncMigrateFullDataSetCachePolicy.cs index e407ab8a..356e4837 100644 --- a/uSync.Core/Migrations/SyncMigrateFullDataSetCachePolicy.cs +++ b/uSync.Core/Migrations/SyncMigrateFullDataSetCachePolicy.cs @@ -10,7 +10,7 @@ namespace uSync.Core.Migrations; /// -/// this is simliar to the SyncDataCachePolicy, except everything is cached in one key, +/// this is similar to the SyncDataCachePolicy, except everything is cached in one key, /// /// /// caching all entites, works when it is unlikely they will change much duing the lookup From de2e81406f675d55318cd9f7e970fcc1cbbc0f9f Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Wed, 25 Mar 2026 10:43:19 +0000 Subject: [PATCH 06/18] tidy up cache files. --- .../SyncMigratedDataBuilderExtensions.cs | 2 +- .../Migrations/SyncMigratedDataRepository.cs | 5 ++- .../ISyncDataRepositoryCachePolicy.cs | 2 +- .../ISyncFullDataSetRepositoryCachePolicy.cs} | 4 +- .../Cache/ISyncMigratedDataCachePolicy.cs | 7 +++ .../ISyncMigratedFullDataSetCachePolicy.cs | 9 ++++ .../SyncDataRepositoryCachePolicy.cs | 5 ++- .../SyncFullDataSetRepositoryCachePolicy.cs} | 29 +++++++----- .../Cache/SyncMigratedDataCachePolicy.cs | 22 ++++++++++ .../SyncMigratedFullDataSetCachePolicy.cs | 18 ++++++++ .../Persistance/SyncDataRespositoryBase.cs | 12 ++--- .../SyncMigratedDataCachePolicy.cs | 44 ------------------- 12 files changed, 90 insertions(+), 69 deletions(-) rename uSync.Core/Persistance/{ => Cache}/ISyncDataRepositoryCachePolicy.cs (95%) rename uSync.Core/Persistance/{ISyncDataFullSetCachePolicy.cs => Cache/ISyncFullDataSetRepositoryCachePolicy.cs} (83%) create mode 100644 uSync.Core/Persistance/Cache/ISyncMigratedDataCachePolicy.cs create mode 100644 uSync.Core/Persistance/Cache/ISyncMigratedFullDataSetCachePolicy.cs rename uSync.Core/Persistance/{ => Cache}/SyncDataRepositoryCachePolicy.cs (97%) rename uSync.Core/{Migrations/SyncMigrateFullDataSetCachePolicy.cs => Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs} (84%) create mode 100644 uSync.Core/Persistance/Cache/SyncMigratedDataCachePolicy.cs create mode 100644 uSync.Core/Persistance/Cache/SyncMigratedFullDataSetCachePolicy.cs delete mode 100644 uSync.Core/Persistance/SyncMigratedDataCachePolicy.cs diff --git a/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs b/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs index 2e108e50..bce17e6b 100644 --- a/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs +++ b/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs @@ -10,7 +10,7 @@ using Umbraco.Cms.Infrastructure.Migrations.Upgrade; using uSync.Core.Migrations.Migrations; -using uSync.Core.Persistance; +using uSync.Core.Persistance.Cache; namespace uSync.Core.Migrations; diff --git a/uSync.Core/Migrations/SyncMigratedDataRepository.cs b/uSync.Core/Migrations/SyncMigratedDataRepository.cs index 09d77524..47526541 100644 --- a/uSync.Core/Migrations/SyncMigratedDataRepository.cs +++ b/uSync.Core/Migrations/SyncMigratedDataRepository.cs @@ -4,6 +4,7 @@ using Umbraco.Cms.Infrastructure.Scoping; using uSync.Core.Persistance; +using uSync.Core.Persistance.Cache; namespace uSync.Core.Migrations; @@ -16,7 +17,7 @@ public SyncMigratedDataRepository( AppCaches appCaches, ISyncMigratedFullDataSetCachePolicy cachePolicy, ILogger logger) - : base(scopeAccessor, appCaches, cachePolicy, - SyncMigrations.MigratedDataTableName, logger) + : base(scopeAccessor, logger, appCaches, + cachePolicy, SyncMigrations.MigratedDataTableName) { } } diff --git a/uSync.Core/Persistance/ISyncDataRepositoryCachePolicy.cs b/uSync.Core/Persistance/Cache/ISyncDataRepositoryCachePolicy.cs similarity index 95% rename from uSync.Core/Persistance/ISyncDataRepositoryCachePolicy.cs rename to uSync.Core/Persistance/Cache/ISyncDataRepositoryCachePolicy.cs index 211f665d..44d35a25 100644 --- a/uSync.Core/Persistance/ISyncDataRepositoryCachePolicy.cs +++ b/uSync.Core/Persistance/Cache/ISyncDataRepositoryCachePolicy.cs @@ -1,4 +1,4 @@ -namespace uSync.Core.Persistance; +namespace uSync.Core.Persistance.Cache; public interface ISyncDataRepositoryCachePolicy where TModel : class, ISyncDataEntity { diff --git a/uSync.Core/Persistance/ISyncDataFullSetCachePolicy.cs b/uSync.Core/Persistance/Cache/ISyncFullDataSetRepositoryCachePolicy.cs similarity index 83% rename from uSync.Core/Persistance/ISyncDataFullSetCachePolicy.cs rename to uSync.Core/Persistance/Cache/ISyncFullDataSetRepositoryCachePolicy.cs index aba51edd..ebd9a76d 100644 --- a/uSync.Core/Persistance/ISyncDataFullSetCachePolicy.cs +++ b/uSync.Core/Persistance/Cache/ISyncFullDataSetRepositoryCachePolicy.cs @@ -1,6 +1,6 @@ -namespace uSync.Core.Persistance; +namespace uSync.Core.Persistance.Cache; -public interface ISyncDataFullSetCachePolicy where TModel : class, ISyncDataEntity +public interface ISyncFullDataSetRepositoryCachePolicy where TModel : class, ISyncDataEntity { void ClearAllAsync(); Task CreateAsync(TModel model, Func persistNewAsync, CancellationToken cancellationToken = default); diff --git a/uSync.Core/Persistance/Cache/ISyncMigratedDataCachePolicy.cs b/uSync.Core/Persistance/Cache/ISyncMigratedDataCachePolicy.cs new file mode 100644 index 00000000..96dadc85 --- /dev/null +++ b/uSync.Core/Persistance/Cache/ISyncMigratedDataCachePolicy.cs @@ -0,0 +1,7 @@ +using uSync.Core.Migrations; + +namespace uSync.Core.Persistance.Cache; + +public interface ISyncMigratedDataCachePolicy + : ISyncDataRepositoryCachePolicy +{ } diff --git a/uSync.Core/Persistance/Cache/ISyncMigratedFullDataSetCachePolicy.cs b/uSync.Core/Persistance/Cache/ISyncMigratedFullDataSetCachePolicy.cs new file mode 100644 index 00000000..71162845 --- /dev/null +++ b/uSync.Core/Persistance/Cache/ISyncMigratedFullDataSetCachePolicy.cs @@ -0,0 +1,9 @@ +using uSync.Core.Migrations; + +namespace uSync.Core.Persistance.Cache; + +public interface ISyncMigratedFullDataSetCachePolicy + : ISyncFullDataSetRepositoryCachePolicy +{ + +} \ No newline at end of file diff --git a/uSync.Core/Persistance/SyncDataRepositoryCachePolicy.cs b/uSync.Core/Persistance/Cache/SyncDataRepositoryCachePolicy.cs similarity index 97% rename from uSync.Core/Persistance/SyncDataRepositoryCachePolicy.cs rename to uSync.Core/Persistance/Cache/SyncDataRepositoryCachePolicy.cs index 3e2f25eb..3d481c94 100644 --- a/uSync.Core/Persistance/SyncDataRepositoryCachePolicy.cs +++ b/uSync.Core/Persistance/Cache/SyncDataRepositoryCachePolicy.cs @@ -6,9 +6,10 @@ using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope; -namespace uSync.Core.Persistance; +namespace uSync.Core.Persistance.Cache; -internal class SyncDataRepositoryCachePolicy : ISyncDataRepositoryCachePolicy where TModel : class, ISyncDataEntity +internal class SyncDataRepositoryCachePolicy + : ISyncDataRepositoryCachePolicy where TModel : class, ISyncDataEntity { private readonly IAppPolicyCache _globalCache; private readonly IScopeAccessor _scopeAccessor; diff --git a/uSync.Core/Migrations/SyncMigrateFullDataSetCachePolicy.cs b/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs similarity index 84% rename from uSync.Core/Migrations/SyncMigrateFullDataSetCachePolicy.cs rename to uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs index 356e4837..851b63e8 100644 --- a/uSync.Core/Migrations/SyncMigrateFullDataSetCachePolicy.cs +++ b/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs @@ -3,11 +3,9 @@ using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; -using uSync.Core.Persistance; - using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope; -namespace uSync.Core.Migrations; +namespace uSync.Core.Persistance.Cache; /// /// this is similar to the SyncDataCachePolicy, except everything is cached in one key, @@ -17,16 +15,18 @@ namespace uSync.Core.Migrations; /// phase, and there are not a lot (e.g 100+s) of entrires, we can cache them, and then /// all the lookups don't hit the database. /// -internal class SyncMigrateFullDataSetCachePolicy - : ISyncDataFullSetCachePolicy +internal class SyncFullDataSetRepositoryCachePolicy + : ISyncFullDataSetRepositoryCachePolicy where TModel : class, ISyncDataEntity { + private const int _cacheDurationMinutes = 10; + private readonly IAppPolicyCache _globalCache; private readonly IScopeAccessor _scopeAccessor; private readonly IRepositoryCacheVersionService _repositoryCacheVersionService; private readonly ICacheSyncService _cacheSyncService; - public SyncMigrateFullDataSetCachePolicy( + public SyncFullDataSetRepositoryCachePolicy( IAppPolicyCache globalCache, IScopeAccessor scopeAccessor, IRepositoryCacheVersionService repositoryCacheVersionService, @@ -67,33 +67,37 @@ public async Task CreateAsync(TModel model, Func persistNewAsync, finally { ClearAllAsync(); + await RegisterCacheChangeAsync(); } } - public Task DeleteAsync(TModel model, Func persistDeleteAsync, CancellationToken cancellationToken = default) + public async Task DeleteAsync(TModel model, Func persistDeleteAsync, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(model); try { - return persistDeleteAsync(model); + await persistDeleteAsync(model); } finally { ClearAllAsync(); + await RegisterCacheChangeAsync(); } } - public Task UpdateAsync(TModel model, Func persistUpdateAsync, CancellationToken cancellationToken = default) + public async Task UpdateAsync(TModel model, Func persistUpdateAsync, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(model); try { - return persistUpdateAsync(model); + await persistUpdateAsync(model); } finally { ClearAllAsync(); + await RegisterCacheChangeAsync(); } + } public async Task ExistsAsync(TKey key, Func>> performGetAllAsync, CancellationToken cancellationToken = default) @@ -155,7 +159,7 @@ private async Task GetAllCached(Func>> perfor private Task InsertCacheEntries(TModel[] entries) { - Cache.Insert(_dataSetCacheKey, () => entries, TimeSpan.FromMinutes(10), true); + Cache.Insert(_dataSetCacheKey, () => entries, TimeSpan.FromMinutes(_cacheDurationMinutes), true); return Task.CompletedTask; } @@ -168,5 +172,6 @@ private async Task EnsureCacheIsSyncedAsync() _cacheSyncService.SyncInternal(CancellationToken.None); } - + private async Task RegisterCacheChangeAsync() + => await _repositoryCacheVersionService.SetCacheUpdatedAsync(); } diff --git a/uSync.Core/Persistance/Cache/SyncMigratedDataCachePolicy.cs b/uSync.Core/Persistance/Cache/SyncMigratedDataCachePolicy.cs new file mode 100644 index 00000000..d5330dff --- /dev/null +++ b/uSync.Core/Persistance/Cache/SyncMigratedDataCachePolicy.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Infrastructure.Scoping; + +using uSync.Core.Migrations; + +namespace uSync.Core.Persistance.Cache; + +internal class SyncMigratedDataCachePolicy : SyncDataRepositoryCachePolicy + , ISyncMigratedDataCachePolicy +{ + public SyncMigratedDataCachePolicy( + IAppPolicyCache globalCache, + IScopeAccessor scopeAccessor, + IRepositoryCacheVersionService repositoryCacheVersionService, + ICacheSyncService cacheSyncService) + : base(globalCache, scopeAccessor, repositoryCacheVersionService, cacheSyncService) + { } +} diff --git a/uSync.Core/Persistance/Cache/SyncMigratedFullDataSetCachePolicy.cs b/uSync.Core/Persistance/Cache/SyncMigratedFullDataSetCachePolicy.cs new file mode 100644 index 00000000..f08f34a0 --- /dev/null +++ b/uSync.Core/Persistance/Cache/SyncMigratedFullDataSetCachePolicy.cs @@ -0,0 +1,18 @@ +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Infrastructure.Scoping; + +using uSync.Core.Migrations; + +namespace uSync.Core.Persistance.Cache; + +internal class SyncMigratedFullDataSetCachePolicy : SyncFullDataSetRepositoryCachePolicy + , ISyncMigratedFullDataSetCachePolicy +{ + public SyncMigratedFullDataSetCachePolicy( + IAppPolicyCache globalCache, + IScopeAccessor scopeAccessor, + IRepositoryCacheVersionService repositoryCacheVersionService, + ICacheSyncService cacheSyncService) + : base(globalCache, scopeAccessor, repositoryCacheVersionService, cacheSyncService) + { } +} diff --git a/uSync.Core/Persistance/SyncDataRespositoryBase.cs b/uSync.Core/Persistance/SyncDataRespositoryBase.cs index 2e8954c8..baed9797 100644 --- a/uSync.Core/Persistance/SyncDataRespositoryBase.cs +++ b/uSync.Core/Persistance/SyncDataRespositoryBase.cs @@ -8,6 +8,8 @@ using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; +using uSync.Core.Persistance.Cache; + namespace uSync.Core.Persistance; /// @@ -22,7 +24,7 @@ namespace uSync.Core.Persistance; internal abstract class SyncDataRespositoryBase : ISyncDataRespository where TModel : class, ISyncDataEntity { - protected readonly ISyncDataFullSetCachePolicy _cachePolicy; + protected readonly ISyncFullDataSetRepositoryCachePolicy _cachePolicy; protected readonly IScopeAccessor _scopeAccessor; protected readonly AppCaches _appCaches; protected readonly ILogger> _logger; @@ -31,17 +33,17 @@ internal abstract class SyncDataRespositoryBase : ISyncDataResposit public SyncDataRespositoryBase( IScopeAccessor scopeAccessor, + ILogger> logger, AppCaches appCaches, - ISyncDataFullSetCachePolicy cachePolicy, - string tableName, - ILogger> logger) + ISyncFullDataSetRepositoryCachePolicy cachePolicy, + string tableName) { _scopeAccessor = scopeAccessor; + _logger = logger; _appCaches = appCaches; _cachePolicy = cachePolicy; _tableName = tableName; - _logger = logger; } protected IScope AmbientScope diff --git a/uSync.Core/Persistance/SyncMigratedDataCachePolicy.cs b/uSync.Core/Persistance/SyncMigratedDataCachePolicy.cs deleted file mode 100644 index 44a363ca..00000000 --- a/uSync.Core/Persistance/SyncMigratedDataCachePolicy.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Infrastructure.Scoping; - -using uSync.Core.Migrations; - -namespace uSync.Core.Persistance; - -internal class SyncMigratedDataCachePolicy : SyncDataRepositoryCachePolicy - , ISyncMigratedDataCachePolicy -{ - public SyncMigratedDataCachePolicy( - IAppPolicyCache globalCache, - IScopeAccessor scopeAccessor, - IRepositoryCacheVersionService repositoryCacheVersionService, - ICacheSyncService cacheSyncService) - : base(globalCache, scopeAccessor, repositoryCacheVersionService, cacheSyncService) - { } -} - -public interface ISyncMigratedDataCachePolicy - : ISyncDataRepositoryCachePolicy -{ } - -internal class SyncMigratedFullDataSetCachePolicy : SyncMigrateFullDataSetCachePolicy - , ISyncMigratedFullDataSetCachePolicy -{ - public SyncMigratedFullDataSetCachePolicy( - IAppPolicyCache globalCache, - IScopeAccessor scopeAccessor, - IRepositoryCacheVersionService repositoryCacheVersionService, - ICacheSyncService cacheSyncService) - : base(globalCache, scopeAccessor, repositoryCacheVersionService, cacheSyncService) - { } -} - -public interface ISyncMigratedFullDataSetCachePolicy - : ISyncDataFullSetCachePolicy -{ - -} \ No newline at end of file From d30b6a4c4d894495e095fc4cb1cf231f17bfc37c Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Wed, 25 Mar 2026 20:10:47 +0000 Subject: [PATCH 07/18] clean up semaphore logic, so it can timeout (eventually) --- .../Cache/SyncFullDataSetRepositoryCachePolicy.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs b/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs index 851b63e8..bdef9733 100644 --- a/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs +++ b/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs @@ -131,17 +131,24 @@ public async Task GetAllAsync(TKey[]? keys, Func x.Key?.Equals(key) is true); } - SemaphoreSlim _semaphoreLock = new SemaphoreSlim(1); + private static readonly SemaphoreSlim _semaphoreLock = new SemaphoreSlim(1,1); + private static readonly TimeSpan _semaphoreLockTimeout = TimeSpan.FromSeconds(90); + private async Task GetAllCached(Func>> performGetAllAsync, CancellationToken cancellationToken) { var all = Cache.GetCacheItem(_dataSetCacheKey); if (all is not null) return all; + if (await _semaphoreLock.WaitAsync(_semaphoreLockTimeout, cancellationToken) is false) + { + // we don't want to cause issues if the cache is missing, so return nothing, this is the most likely outcome, + // and the processes will all still happen you just might miss some of the migrating value mappers, but that is better than locking up the system. + return []; + } + try { - await _semaphoreLock.WaitAsync(cancellationToken); - // try in the lock, possible something else filled it in while we waited. all = Cache.GetCacheItem(_dataSetCacheKey); if (all is not null) return all; From 0b0ce2552a2a31e18c7cfef12bbb0be2568da6f6 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Wed, 25 Mar 2026 21:51:46 +0000 Subject: [PATCH 08/18] Update uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs b/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs index bdef9733..7851866c 100644 --- a/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs +++ b/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs @@ -11,7 +11,7 @@ namespace uSync.Core.Persistance.Cache; /// this is similar to the SyncDataCachePolicy, except everything is cached in one key, /// /// -/// caching all entites, works when it is unlikely they will change much duing the lookup +/// caching all entites, works when it is unlikely they will change much during the lookup /// phase, and there are not a lot (e.g 100+s) of entrires, we can cache them, and then /// all the lookups don't hit the database. /// From 352e4502e5b6a66269229133c8b751daaefecc3b Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Wed, 25 Mar 2026 21:53:44 +0000 Subject: [PATCH 09/18] Update uSync.Core/Migrations/SyncMigratedDataRepository.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- uSync.Core/Migrations/SyncMigratedDataRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uSync.Core/Migrations/SyncMigratedDataRepository.cs b/uSync.Core/Migrations/SyncMigratedDataRepository.cs index 47526541..7613ea1d 100644 --- a/uSync.Core/Migrations/SyncMigratedDataRepository.cs +++ b/uSync.Core/Migrations/SyncMigratedDataRepository.cs @@ -16,7 +16,7 @@ public SyncMigratedDataRepository( IScopeAccessor scopeAccessor, AppCaches appCaches, ISyncMigratedFullDataSetCachePolicy cachePolicy, - ILogger logger) + ILogger> logger) : base(scopeAccessor, logger, appCaches, cachePolicy, SyncMigrations.MigratedDataTableName) { } From 714eb63a006d01d45f70c5c4b54477716476463c Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Wed, 25 Mar 2026 21:56:52 +0000 Subject: [PATCH 10/18] Remove the individual cache policy items (we are using full data set cache policies) --- .../Cache/ISyncDataRepositoryCachePolicy.cs | 12 - .../Cache/ISyncMigratedDataCachePolicy.cs | 7 - .../Cache/SyncDataRepositoryCachePolicy.cs | 230 ------------------ .../Cache/SyncMigratedDataCachePolicy.cs | 22 -- 4 files changed, 271 deletions(-) delete mode 100644 uSync.Core/Persistance/Cache/ISyncDataRepositoryCachePolicy.cs delete mode 100644 uSync.Core/Persistance/Cache/ISyncMigratedDataCachePolicy.cs delete mode 100644 uSync.Core/Persistance/Cache/SyncDataRepositoryCachePolicy.cs delete mode 100644 uSync.Core/Persistance/Cache/SyncMigratedDataCachePolicy.cs diff --git a/uSync.Core/Persistance/Cache/ISyncDataRepositoryCachePolicy.cs b/uSync.Core/Persistance/Cache/ISyncDataRepositoryCachePolicy.cs deleted file mode 100644 index 44d35a25..00000000 --- a/uSync.Core/Persistance/Cache/ISyncDataRepositoryCachePolicy.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace uSync.Core.Persistance.Cache; - -public interface ISyncDataRepositoryCachePolicy where TModel : class, ISyncDataEntity -{ - void ClearAllAsync(); - Task CreateAsync(TModel model, Func persistNewAsync, CancellationToken cancellationToken = default); - Task DeleteAsync(TModel model, Func persistDeleteAsync, CancellationToken cancellationToken = default); - Task ExistsAsync(TKey key, Func> performExistsAsync, CancellationToken cancellationToken = default); - Task GetAllAsync(TKey[]? keys, Func>> performGetAllAsync, CancellationToken cancellationToken = default); - Task GetAsync(TKey key, Func> performGetAsync, CancellationToken cancellationToken = default); - Task UpdateAsync(TModel model, Func persistUpdateAsync, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/uSync.Core/Persistance/Cache/ISyncMigratedDataCachePolicy.cs b/uSync.Core/Persistance/Cache/ISyncMigratedDataCachePolicy.cs deleted file mode 100644 index 96dadc85..00000000 --- a/uSync.Core/Persistance/Cache/ISyncMigratedDataCachePolicy.cs +++ /dev/null @@ -1,7 +0,0 @@ -using uSync.Core.Migrations; - -namespace uSync.Core.Persistance.Cache; - -public interface ISyncMigratedDataCachePolicy - : ISyncDataRepositoryCachePolicy -{ } diff --git a/uSync.Core/Persistance/Cache/SyncDataRepositoryCachePolicy.cs b/uSync.Core/Persistance/Cache/SyncDataRepositoryCachePolicy.cs deleted file mode 100644 index 3d481c94..00000000 --- a/uSync.Core/Persistance/Cache/SyncDataRepositoryCachePolicy.cs +++ /dev/null @@ -1,230 +0,0 @@ -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Infrastructure.Scoping; -using Umbraco.Extensions; - -using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope; - -namespace uSync.Core.Persistance.Cache; - -internal class SyncDataRepositoryCachePolicy - : ISyncDataRepositoryCachePolicy where TModel : class, ISyncDataEntity -{ - private readonly IAppPolicyCache _globalCache; - private readonly IScopeAccessor _scopeAccessor; - private readonly IRepositoryCacheVersionService _repositoryCacheVersionService; - private readonly ICacheSyncService _cacheSyncService; - - public SyncDataRepositoryCachePolicy( - IAppPolicyCache globalCache, - IScopeAccessor scopeAccessor, - IRepositoryCacheVersionService repositoryCacheVersionService, - ICacheSyncService cacheSyncService) - { - _globalCache = globalCache; - _scopeAccessor = scopeAccessor; - _repositoryCacheVersionService = repositoryCacheVersionService; - _cacheSyncService = cacheSyncService; - } - - private IAppPolicyCache Cache - { - get - { - IScope? ambientScope = _scopeAccessor.AmbientScope; - return (ambientScope?.RepositoryCacheMode) switch - { - RepositoryCacheMode.Default => _globalCache, - RepositoryCacheMode.Scoped => ambientScope.IsolatedCaches.GetOrCreate(), - RepositoryCacheMode.None => NoAppCache.Instance, - _ => throw new NotSupportedException($"RepositoryCacheMode {ambientScope?.RepositoryCacheMode} is not supported"), - }; - } - } - - - private string _modelCacheKey => $"{nameof(TModel)}"; - private static readonly TModel[] _emptyArray = []; - - public async Task GetAsync(TKey key, Func> performGetAsync, CancellationToken cancellationToken = default) - { - await EnsureCacheIsSyncedAsync(); - var cacheKey = GetCacheKey(key); - - TModel? fromCache = Cache.GetCacheItem(cacheKey); - if (fromCache is not null) return fromCache; - - // Cache the null result to prevent repeated lookups for missing items - if (Cache.GetCacheItem(cacheKey) == Constants.Cache.NullRepresentationInCache) - return null; - - TModel? model = await performGetAsync(key); - - InsertIntoCache(cacheKey, model); - - return model; - } - - public async Task ExistsAsync(TKey key, Func> performExistsAsync, CancellationToken cancellationToken = default) - { - await EnsureCacheIsSyncedAsync(); - var cacheKey = GetCacheKey(key); - TModel? fromCache = Cache.GetCacheItem(cacheKey); - return fromCache is not null || await performExistsAsync(key); - } - - public async Task CreateAsync(TModel model, Func persistNewAsync, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(model); - - try - { - await persistNewAsync(model); - Cache.Insert(GetCacheKey(model), () => model, TimeSpan.FromMinutes(5), true); - } - catch - { - Cache.Clear(GetCacheKey(model)); - Cache.Clear(_modelCacheKey); - throw; - } - - await RegisterCacheChangeAsync(); - } - - public async Task UpdateAsync(TModel model, Func persistUpdateAsync, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(model); - try - { - await persistUpdateAsync(model); - Cache.Insert(GetCacheKey(model), () => model, TimeSpan.FromMinutes(5), true); - } - catch - { - Cache.Clear(GetCacheKey(model)); - Cache.Clear(_modelCacheKey); - throw; - } - - await RegisterCacheChangeAsync(); - } - - public async Task DeleteAsync(TModel model, Func persistDeleteAsync, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(model); - - try - { - await persistDeleteAsync(model); - } - finally - { - Cache.Clear(GetCacheKey(model)); - Cache.Clear(_modelCacheKey); - } - - await RegisterCacheChangeAsync(); - } - - public virtual async Task GetAllAsync(TKey[]? keys, Func>> performGetAllAsync, CancellationToken cancellationToken = default) - { - await EnsureCacheIsSyncedAsync(); - - if (keys?.Length > 0) - { - var items = await keys.ToAsyncEnumerable().Select(async (x, ct) => await GetCached(x, ct)) - .Where(x => x != null) - .ToArrayAsync(); - - if (items != null && keys.Length.Equals(items.Length)) - return [.. items.WhereNotNull()]; - } - else - { - // TODO - we could choose to do a count, (cached vs db) to verify ? - var items = Cache.GetCacheItemsByKeySearch(_modelCacheKey) - .WhereNotNull().ToArray(); - - if (items.Length > 0) - return items; - else - { - // when the cache is empty we check for the special 'empty' one. - TModel[]? empty = Cache.GetCacheItem(_modelCacheKey); - if (empty != null) - return empty; - } - } - - var models = (await performGetAllAsync(keys)) - .WhereNotNull() - .ToArray(); - - InsertIntoCache(models); - - return models ?? []; - } - - private string GetCacheKey(TModel model) => GetCacheKey(model.Key); - - private string GetCacheKey(TKey key) - { - if (EqualityComparer.Default.Equals(key, default)) - return string.Empty; - - if (typeof(TKey).IsValueType) - return $"{_modelCacheKey}_{key}"; - - return $"{_modelCacheKey}_{key?.ToString()?.ToUpperInvariant()}"; - } - - private async Task EnsureCacheIsSyncedAsync() - { - var synced = await _repositoryCacheVersionService.IsCacheSyncedAsync(); - if (synced) return; - - _cacheSyncService.SyncInternal(CancellationToken.None); - } - - - private async Task RegisterCacheChangeAsync() - => await _repositoryCacheVersionService.SetCacheUpdatedAsync(); - - private void InsertIntoCache(string cacheKey, TModel? model) - { - if (model is null) - { - Cache.Insert(cacheKey, () => Constants.Cache.NullRepresentationInCache, TimeSpan.FromMinutes(5), true); - } - else - { - Cache.Insert(cacheKey, () => model, TimeSpan.FromMinutes(5), true); - } - } - - private void InsertIntoCache(TModel[]? models) - { - if (models == null || models.Length == 0) - { - Cache.Insert(_modelCacheKey, () => _emptyArray, TimeSpan.FromMinutes(5), true); - return; - } - - foreach (var model in models) - { - InsertIntoCache(GetCacheKey(model), model); - } - } - - private async Task GetCached(TKey key, CancellationToken cancellationToken) - { - await EnsureCacheIsSyncedAsync(); - var cacheKey = GetCacheKey(key); - return Cache.GetCacheItem(cacheKey); - } - - public void ClearAllAsync() - => Cache.ClearByKey(_modelCacheKey); -} diff --git a/uSync.Core/Persistance/Cache/SyncMigratedDataCachePolicy.cs b/uSync.Core/Persistance/Cache/SyncMigratedDataCachePolicy.cs deleted file mode 100644 index d5330dff..00000000 --- a/uSync.Core/Persistance/Cache/SyncMigratedDataCachePolicy.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Infrastructure.Scoping; - -using uSync.Core.Migrations; - -namespace uSync.Core.Persistance.Cache; - -internal class SyncMigratedDataCachePolicy : SyncDataRepositoryCachePolicy - , ISyncMigratedDataCachePolicy -{ - public SyncMigratedDataCachePolicy( - IAppPolicyCache globalCache, - IScopeAccessor scopeAccessor, - IRepositoryCacheVersionService repositoryCacheVersionService, - ICacheSyncService cacheSyncService) - : base(globalCache, scopeAccessor, repositoryCacheVersionService, cacheSyncService) - { } -} From 430b4095a1dd93fbab93fa27570f65276b51f94c Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Wed, 25 Mar 2026 22:00:46 +0000 Subject: [PATCH 11/18] tidy up cache polcy files. --- .../Cache/ISyncMigratedFullDataSetCachePolicy.cs | 4 ++-- .../Cache/SyncMigratedFullDataSetCachePolicy.cs | 4 ++-- uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs | 2 +- uSync.Core/Migrations/SyncMigratedDataRepository.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename uSync.Core/{Persistance => Migrations}/Cache/ISyncMigratedFullDataSetCachePolicy.cs (63%) rename uSync.Core/{Persistance => Migrations}/Cache/SyncMigratedFullDataSetCachePolicy.cs (88%) diff --git a/uSync.Core/Persistance/Cache/ISyncMigratedFullDataSetCachePolicy.cs b/uSync.Core/Migrations/Cache/ISyncMigratedFullDataSetCachePolicy.cs similarity index 63% rename from uSync.Core/Persistance/Cache/ISyncMigratedFullDataSetCachePolicy.cs rename to uSync.Core/Migrations/Cache/ISyncMigratedFullDataSetCachePolicy.cs index 71162845..9c3b6ca6 100644 --- a/uSync.Core/Persistance/Cache/ISyncMigratedFullDataSetCachePolicy.cs +++ b/uSync.Core/Migrations/Cache/ISyncMigratedFullDataSetCachePolicy.cs @@ -1,6 +1,6 @@ -using uSync.Core.Migrations; +using uSync.Core.Persistance.Cache; -namespace uSync.Core.Persistance.Cache; +namespace uSync.Core.Migrations.Cache; public interface ISyncMigratedFullDataSetCachePolicy : ISyncFullDataSetRepositoryCachePolicy diff --git a/uSync.Core/Persistance/Cache/SyncMigratedFullDataSetCachePolicy.cs b/uSync.Core/Migrations/Cache/SyncMigratedFullDataSetCachePolicy.cs similarity index 88% rename from uSync.Core/Persistance/Cache/SyncMigratedFullDataSetCachePolicy.cs rename to uSync.Core/Migrations/Cache/SyncMigratedFullDataSetCachePolicy.cs index f08f34a0..19c5b51b 100644 --- a/uSync.Core/Persistance/Cache/SyncMigratedFullDataSetCachePolicy.cs +++ b/uSync.Core/Migrations/Cache/SyncMigratedFullDataSetCachePolicy.cs @@ -1,9 +1,9 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Infrastructure.Scoping; -using uSync.Core.Migrations; +using uSync.Core.Persistance.Cache; -namespace uSync.Core.Persistance.Cache; +namespace uSync.Core.Migrations.Cache; internal class SyncMigratedFullDataSetCachePolicy : SyncFullDataSetRepositoryCachePolicy , ISyncMigratedFullDataSetCachePolicy diff --git a/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs b/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs index bce17e6b..379b656d 100644 --- a/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs +++ b/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs @@ -9,8 +9,8 @@ using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Migrations.Upgrade; +using uSync.Core.Migrations.Cache; using uSync.Core.Migrations.Migrations; -using uSync.Core.Persistance.Cache; namespace uSync.Core.Migrations; diff --git a/uSync.Core/Migrations/SyncMigratedDataRepository.cs b/uSync.Core/Migrations/SyncMigratedDataRepository.cs index 7613ea1d..07bfc035 100644 --- a/uSync.Core/Migrations/SyncMigratedDataRepository.cs +++ b/uSync.Core/Migrations/SyncMigratedDataRepository.cs @@ -3,8 +3,8 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Infrastructure.Scoping; +using uSync.Core.Migrations.Cache; using uSync.Core.Persistance; -using uSync.Core.Persistance.Cache; namespace uSync.Core.Migrations; From ad75caa53f242d62c45dd74a863675d6a662231a Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Wed, 25 Mar 2026 22:10:08 +0000 Subject: [PATCH 12/18] Update uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Cache/SyncFullDataSetRepositoryCachePolicy.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs b/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs index 7851866c..96ae3682 100644 --- a/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs +++ b/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs @@ -142,9 +142,10 @@ private async Task GetAllCached(Func>> perfor if (await _semaphoreLock.WaitAsync(_semaphoreLockTimeout, cancellationToken) is false) { - // we don't want to cause issues if the cache is missing, so return nothing, this is the most likely outcome, - // and the processes will all still happen you just might miss some of the migrating value mappers, but that is better than locking up the system. - return []; + // we don't want to cause issues if the cache is missing or contended, + // so avoid blocking and fall back to fetching the data directly without caching. + TModel[] entries = [.. (await performGetAllAsync())]; + return entries; } try From 87993536703442cbefa532574326740bff9c76b4 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Wed, 25 Mar 2026 22:10:15 +0000 Subject: [PATCH 13/18] Update uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Cache/SyncFullDataSetRepositoryCachePolicy.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs b/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs index 96ae3682..06422b8c 100644 --- a/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs +++ b/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs @@ -8,11 +8,11 @@ namespace uSync.Core.Persistance.Cache; /// -/// this is similar to the SyncDataCachePolicy, except everything is cached in one key, +/// this is similar to the SyncDataCachePolicy, except everything is cached in one key. /// /// -/// caching all entites, works when it is unlikely they will change much during the lookup -/// phase, and there are not a lot (e.g 100+s) of entrires, we can cache them, and then +/// caching all entities works when it is unlikely they will change much during the lookup +/// phase, and there are not a lot (e.g 100+s) of entries, we can cache them, and then /// all the lookups don't hit the database. /// internal class SyncFullDataSetRepositoryCachePolicy From 90a59d51b34a66885d1b0fcdaad8592f54e5994a Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Wed, 25 Mar 2026 22:27:30 +0000 Subject: [PATCH 14/18] Clean the migrated table when someone does a clean export. --- uSync.BackOffice/Services/SyncService.cs | 4 +++ .../Migrations/ISyncMigratedDataRepository.cs | 1 + .../Migrations/ISyncMigratedDataService.cs | 1 + .../SyncExportCleanNotification.cs | 11 ++++++++ .../SyncExportCleanNotificationHandler.cs | 18 +++++++++++++ .../SyncMigratedDataBuilderExtensions.cs | 25 ++++++++++++++++--- .../Migrations/SyncMigratedDataRepository.cs | 13 ++++++++++ .../Migrations/SyncMigratedDataService.cs | 18 ++++++++++--- 8 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 uSync.Core/Migrations/Notifications/SyncExportCleanNotification.cs create mode 100644 uSync.Core/Migrations/Notifications/SyncExportCleanNotificationHandler.cs diff --git a/uSync.BackOffice/Services/SyncService.cs b/uSync.BackOffice/Services/SyncService.cs index e65c6161..0205bf90 100644 --- a/uSync.BackOffice/Services/SyncService.cs +++ b/uSync.BackOffice/Services/SyncService.cs @@ -26,6 +26,7 @@ using uSync.BackOffice.SyncHandlers.Models; using uSync.Core; using uSync.Core.Extensions; +using uSync.Core.Migrations.Notifications; using uSync.Core.Serialization; namespace uSync.BackOffice; @@ -297,6 +298,9 @@ public bool CleanExportFolder(string folder) { if (_syncFileService.DirectoryExists(folder)) _syncFileService.CleanFolder(folder); + + // tell the migrations table to clean itself too. + _eventAggregator.Publish(new SyncExportCleanNotification()); } catch (Exception ex) { diff --git a/uSync.Core/Migrations/ISyncMigratedDataRepository.cs b/uSync.Core/Migrations/ISyncMigratedDataRepository.cs index d26005a3..b75c774f 100644 --- a/uSync.Core/Migrations/ISyncMigratedDataRepository.cs +++ b/uSync.Core/Migrations/ISyncMigratedDataRepository.cs @@ -4,4 +4,5 @@ namespace uSync.Core.Migrations; public interface ISyncMigratedDataRepository : ISyncDataRespository { + Task DeleteAllAsync(); } diff --git a/uSync.Core/Migrations/ISyncMigratedDataService.cs b/uSync.Core/Migrations/ISyncMigratedDataService.cs index 4311a500..81ecd80f 100644 --- a/uSync.Core/Migrations/ISyncMigratedDataService.cs +++ b/uSync.Core/Migrations/ISyncMigratedDataService.cs @@ -5,5 +5,6 @@ namespace uSync.Core.Migrations; public interface ISyncMigratedDataService : ISyncDataService { Task AddRename(string newKey, string oldKey, string? additionalData); + Task DeleteAllAsync(); Task GetOriginalKeyAsync(string key); } \ No newline at end of file diff --git a/uSync.Core/Migrations/Notifications/SyncExportCleanNotification.cs b/uSync.Core/Migrations/Notifications/SyncExportCleanNotification.cs new file mode 100644 index 00000000..c86ae716 --- /dev/null +++ b/uSync.Core/Migrations/Notifications/SyncExportCleanNotification.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +using Umbraco.Cms.Core.Notifications; + +namespace uSync.Core.Migrations.Notifications; + +public class SyncExportCleanNotification : INotification +{ +} diff --git a/uSync.Core/Migrations/Notifications/SyncExportCleanNotificationHandler.cs b/uSync.Core/Migrations/Notifications/SyncExportCleanNotificationHandler.cs new file mode 100644 index 00000000..df7cfb50 --- /dev/null +++ b/uSync.Core/Migrations/Notifications/SyncExportCleanNotificationHandler.cs @@ -0,0 +1,18 @@ +using Umbraco.Cms.Core.Events; + +namespace uSync.Core.Migrations.Notifications; + +internal class SyncExportCleanNotificationHandler : INotificationAsyncHandler +{ + private readonly ISyncMigratedDataService _syncMigratedDataService; + + public SyncExportCleanNotificationHandler(ISyncMigratedDataService syncMigratedDataService) + { + _syncMigratedDataService = syncMigratedDataService; + } + + public async Task HandleAsync(SyncExportCleanNotification notification, CancellationToken cancellationToken) + { + await _syncMigratedDataService.DeleteAllAsync(); + } +} diff --git a/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs b/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs index 379b656d..65bd97ea 100644 --- a/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs +++ b/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.DependencyInjection; @@ -11,6 +12,7 @@ using uSync.Core.Migrations.Cache; using uSync.Core.Migrations.Migrations; +using uSync.Core.Migrations.Notifications; namespace uSync.Core.Migrations; @@ -22,6 +24,7 @@ public static IUmbracoBuilder AddSyncMigratedData(this IUmbracoBuilder builder) builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.AddNotificationAsyncHandler(); + builder.AddNotificationAsyncHandler(); return builder; } @@ -33,17 +36,20 @@ internal class SyncMigratedDataMigrationHandler : INotificationAsyncHandler _logger; public SyncMigratedDataMigrationHandler( ICoreScopeProvider scopeProvider, IKeyValueService keyValueService, IRuntimeState runtimeState, - IMigrationPlanExecutor migrationPlanExecutor) + IMigrationPlanExecutor migrationPlanExecutor, + ILogger logger) { _scopeProvider = scopeProvider; _keyValueService = keyValueService; _runtimeState = runtimeState; _migrationPlanExecutor = migrationPlanExecutor; + _logger = logger; } public async Task HandleAsync(UmbracoApplicationStartingNotification notification, CancellationToken cancellationToken) @@ -51,8 +57,21 @@ public async Task HandleAsync(UmbracoApplicationStartingNotification notificatio // we don't run our migration until the site has been installed / isn't upgrading. if (_runtimeState.Level < RuntimeLevel.Run) return; - var upgrader = new Upgrader(new SyncMigratedDataMigrationPlan()); - await upgrader.ExecuteAsync(_migrationPlanExecutor, _scopeProvider, _keyValueService); + // a slightly roundabout way of calling the migration plan + var plan = new SyncMigratedDataMigrationPlan(); + var upgrader = new Upgrader(plan); + // but here we can pre-check if the migration needs to happen. + // and we reduce the amount of logging that appears at startup if it doesn't. + var currentState = _keyValueService.GetValue(upgrader.StateValueKey); + if (currentState == null || currentState != plan.FinalState) + { + await upgrader.ExecuteAsync(_migrationPlanExecutor, _scopeProvider, _keyValueService); + } + else + { + if (_logger.IsEnabled(LogLevel.Debug)) + _logger.LogDebug("{migration} Migration skipped as it has already been completed in a previous run.", nameof(SyncMigratedDataMigrationPlan)); + } } } \ No newline at end of file diff --git a/uSync.Core/Migrations/SyncMigratedDataRepository.cs b/uSync.Core/Migrations/SyncMigratedDataRepository.cs index 07bfc035..d232ca3f 100644 --- a/uSync.Core/Migrations/SyncMigratedDataRepository.cs +++ b/uSync.Core/Migrations/SyncMigratedDataRepository.cs @@ -2,6 +2,7 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Infrastructure.Scoping; +using Umbraco.Extensions; using uSync.Core.Migrations.Cache; using uSync.Core.Persistance; @@ -20,4 +21,16 @@ public SyncMigratedDataRepository( : base(scopeAccessor, logger, appCaches, cachePolicy, SyncMigrations.MigratedDataTableName) { } + + public async Task DeleteAllAsync() + { + var sql = Sql().Delete() + .From(); + + using(var transaction = Database.GetTransaction()) + { + _ = await Database.ExecuteScalarAsync(sql); + transaction.Complete(); + } + } } diff --git a/uSync.Core/Migrations/SyncMigratedDataService.cs b/uSync.Core/Migrations/SyncMigratedDataService.cs index 3725f297..f1017f20 100644 --- a/uSync.Core/Migrations/SyncMigratedDataService.cs +++ b/uSync.Core/Migrations/SyncMigratedDataService.cs @@ -7,10 +7,14 @@ namespace uSync.Core.Migrations; internal class SyncMigratedDataService : SyncDataServiceBase, ISyncMigratedDataService { + private readonly ISyncMigratedDataRepository _migratedRepository; + public SyncMigratedDataService( - ISyncMigratedDataRepository repository, - ICoreScopeProvider scopeProvider) : base(repository, scopeProvider) - { } + ISyncMigratedDataRepository migratedRepository, + ICoreScopeProvider scopeProvider) : base(migratedRepository, scopeProvider) + { + _migratedRepository = migratedRepository; + } /// /// tells us if this property has had it's id migrated, @@ -31,4 +35,12 @@ public async Task AddRename(string newKey, string oldKey, string? additionalData }; await SaveAsync(item); } + + public async Task DeleteAllAsync() + { + using(var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + await _migratedRepository.DeleteAllAsync(); + } + } } From 5be4ded06dccc7c1612dfb562ce5e19ce9ae4c35 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 26 Mar 2026 08:26:49 +0000 Subject: [PATCH 15/18] move deleteall into base service (and fix cache notification) --- .../Migrations/ISyncMigratedDataRepository.cs | 4 +--- .../Migrations/SyncMigratedDataRepository.cs | 13 +------------ .../Migrations/SyncMigratedDataService.cs | 9 +-------- .../ISyncFullDataSetRepositoryCachePolicy.cs | 1 + .../SyncFullDataSetRepositoryCachePolicy.cs | 13 +++++++++++++ uSync.Core/Persistance/ISyncDataRespository.cs | 1 + uSync.Core/Persistance/ISyncDataService.cs | 1 + .../Persistance/SyncDataRespositoryBase.cs | 17 +++++++++++++++++ uSync.Core/Persistance/SyncDataServiceBase.cs | 8 ++++++++ 9 files changed, 44 insertions(+), 23 deletions(-) diff --git a/uSync.Core/Migrations/ISyncMigratedDataRepository.cs b/uSync.Core/Migrations/ISyncMigratedDataRepository.cs index b75c774f..c584c3b6 100644 --- a/uSync.Core/Migrations/ISyncMigratedDataRepository.cs +++ b/uSync.Core/Migrations/ISyncMigratedDataRepository.cs @@ -3,6 +3,4 @@ namespace uSync.Core.Migrations; public interface ISyncMigratedDataRepository : ISyncDataRespository -{ - Task DeleteAllAsync(); -} +{ } \ No newline at end of file diff --git a/uSync.Core/Migrations/SyncMigratedDataRepository.cs b/uSync.Core/Migrations/SyncMigratedDataRepository.cs index d232ca3f..b12987b3 100644 --- a/uSync.Core/Migrations/SyncMigratedDataRepository.cs +++ b/uSync.Core/Migrations/SyncMigratedDataRepository.cs @@ -21,16 +21,5 @@ public SyncMigratedDataRepository( : base(scopeAccessor, logger, appCaches, cachePolicy, SyncMigrations.MigratedDataTableName) { } - - public async Task DeleteAllAsync() - { - var sql = Sql().Delete() - .From(); - - using(var transaction = Database.GetTransaction()) - { - _ = await Database.ExecuteScalarAsync(sql); - transaction.Complete(); - } - } + } diff --git a/uSync.Core/Migrations/SyncMigratedDataService.cs b/uSync.Core/Migrations/SyncMigratedDataService.cs index f1017f20..ff47cbcd 100644 --- a/uSync.Core/Migrations/SyncMigratedDataService.cs +++ b/uSync.Core/Migrations/SyncMigratedDataService.cs @@ -35,12 +35,5 @@ public async Task AddRename(string newKey, string oldKey, string? additionalData }; await SaveAsync(item); } - - public async Task DeleteAllAsync() - { - using(var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - await _migratedRepository.DeleteAllAsync(); - } - } } + diff --git a/uSync.Core/Persistance/Cache/ISyncFullDataSetRepositoryCachePolicy.cs b/uSync.Core/Persistance/Cache/ISyncFullDataSetRepositoryCachePolicy.cs index ebd9a76d..f899376a 100644 --- a/uSync.Core/Persistance/Cache/ISyncFullDataSetRepositoryCachePolicy.cs +++ b/uSync.Core/Persistance/Cache/ISyncFullDataSetRepositoryCachePolicy.cs @@ -4,6 +4,7 @@ public interface ISyncFullDataSetRepositoryCachePolicy where TMode { void ClearAllAsync(); Task CreateAsync(TModel model, Func persistNewAsync, CancellationToken cancellationToken = default); + Task DeleteAllAsync(Func persistDeleteAllAsync, CancellationToken cancellationToken = default); Task DeleteAsync(TModel model, Func persistDeleteAsync, CancellationToken cancellationToken = default); Task ExistsAsync(TKey key, Func>> performGetAllAsync, CancellationToken cancellationToken = default); Task GetAllAsync(TKey[]? keys, Func>> performGetAllAsync, CancellationToken cancellationToken = default); diff --git a/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs b/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs index 06422b8c..ac1d334a 100644 --- a/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs +++ b/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs @@ -85,6 +85,19 @@ public async Task DeleteAsync(TModel model, Func persistDeleteAsyn } } + public async Task DeleteAllAsync(Func persistDeleteAllAsync, CancellationToken cancellationToken = default) + { + try + { + await persistDeleteAllAsync(); + } + finally + { + ClearAllAsync(); + await RegisterCacheChangeAsync(); + } + } + public async Task UpdateAsync(TModel model, Func persistUpdateAsync, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(model); diff --git a/uSync.Core/Persistance/ISyncDataRespository.cs b/uSync.Core/Persistance/ISyncDataRespository.cs index e4269d9f..3341cc93 100644 --- a/uSync.Core/Persistance/ISyncDataRespository.cs +++ b/uSync.Core/Persistance/ISyncDataRespository.cs @@ -3,6 +3,7 @@ public interface ISyncDataRespository where TModel : class, ISyncDataEntity { Task CreateAsync(TModel item); + Task DeleteAllAsync(); Task DeleteAsync(TModel item); Task ExistsAsync(TKey key); Task> GetAllAsync(params TKey[] keys); diff --git a/uSync.Core/Persistance/ISyncDataService.cs b/uSync.Core/Persistance/ISyncDataService.cs index 78fe74ed..d5a94b50 100644 --- a/uSync.Core/Persistance/ISyncDataService.cs +++ b/uSync.Core/Persistance/ISyncDataService.cs @@ -3,6 +3,7 @@ public interface ISyncDataService where TModel : class, ISyncDataEntity { Task CreateAsync(TModel item); + Task DeleteAllAsync(); Task DeleteAsync(TModel item); Task ExistsAsync(TKey key); Task> GetAllAsync(params TKey[] keys); diff --git a/uSync.Core/Persistance/SyncDataRespositoryBase.cs b/uSync.Core/Persistance/SyncDataRespositoryBase.cs index baed9797..70f05aa6 100644 --- a/uSync.Core/Persistance/SyncDataRespositoryBase.cs +++ b/uSync.Core/Persistance/SyncDataRespositoryBase.cs @@ -86,6 +86,9 @@ public virtual async Task UpdateAsync(TModel item) public virtual async Task DeleteAsync(TModel item) => await _cachePolicy.DeleteAsync(item, PersistDeletedItemAsync); + public virtual async Task DeleteAllAsync() + => await _cachePolicy.DeleteAllAsync(PersistDeleteAllAsync); + public virtual async Task ExistsAsync(Key key) => await _cachePolicy.ExistsAsync(key, PerformGetAllAsync); @@ -152,6 +155,20 @@ private async Task PersistDeletedItemAsync(TModel model) } } + private async Task PersistDeleteAllAsync() + { + try + { + var delete = $"DELETE FROM {_tableName}"; + await Database.ExecuteAsync(delete); + } + catch(Exception ex) + { + _logger.LogWarning(ex, "uSync Migration Delete All Failed"); + return; + } + } + private async Task> PerformGetAllAsync() { try diff --git a/uSync.Core/Persistance/SyncDataServiceBase.cs b/uSync.Core/Persistance/SyncDataServiceBase.cs index ef530a15..9beac57e 100644 --- a/uSync.Core/Persistance/SyncDataServiceBase.cs +++ b/uSync.Core/Persistance/SyncDataServiceBase.cs @@ -51,6 +51,14 @@ public virtual async Task DeleteAsync(TModel item) scope.Complete(); } + public virtual async Task DeleteAllAsync() + { + using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) + { + await Repository.DeleteAllAsync(); + } + } + public virtual async Task ExistsAsync(TKey key) { using var scope = ScopeProvider.CreateCoreScope(autoComplete: true); From 8d44ab4394d7e71af6d1e9581274688f104fe250 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 26 Mar 2026 11:01:06 +0000 Subject: [PATCH 16/18] Removes DB elements from uSync directly, and add new Tracker to pass functionality out of core. --- uSync.BackOffice/Services/SyncService.cs | 5 +- ...onfigurationSerializerCollectionBuilder.cs | 16 ++ .../DataTypes/IConfigurationSerializer.cs | 5 + uSync.Core/Mapping/ISyncMapper.cs | 1 + uSync.Core/Mapping/SyncValueMapperBase.cs | 3 + .../Mapping/SyncValueMapperCollection.cs | 24 +-- .../Mapping/Tracking/ISyncMapperTracker.cs | 18 ++ .../Tracking/SyncMapperTrackerCollection.cs | 26 +++ .../ISyncMigratedFullDataSetCachePolicy.cs | 9 - .../SyncMigratedFullDataSetCachePolicy.cs | 18 -- .../Migrations/ISyncMigratedDataRepository.cs | 6 - .../Migrations/ISyncMigratedDataService.cs | 10 - .../Migrations/CreateMigratedDataTable.cs | 17 -- .../SyncMigratedDataMigrationPlan.cs | 13 -- .../SyncExportCleanNotification.cs | 11 - .../SyncExportCleanNotificationHandler.cs | 18 -- uSync.Core/Migrations/SyncMigratedData.cs | 32 --- .../SyncMigratedDataBuilderExtensions.cs | 77 ------- .../Migrations/SyncMigratedDataRepository.cs | 25 --- .../Migrations/SyncMigratedDataService.cs | 39 ---- uSync.Core/Migrations/SyncMigrations.cs | 11 - .../SyncExportCleanNotification.cs | 7 + .../ISyncFullDataSetRepositoryCachePolicy.cs | 13 -- .../SyncFullDataSetRepositoryCachePolicy.cs | 198 ------------------ uSync.Core/Persistance/ISyncDataEntity.cs | 10 - .../Persistance/ISyncDataRespository.cs | 12 -- uSync.Core/Persistance/ISyncDataService.cs | 13 -- .../Persistance/SyncDataRespositoryBase.cs | 187 ----------------- uSync.Core/Persistance/SyncDataServiceBase.cs | 79 ------- .../Serializers/DataTypeSerializer.cs | 26 +-- uSync.Core/uSync.Core.csproj | 4 + uSync.Core/uSyncCoreBuilderExtensions.cs | 10 +- 32 files changed, 109 insertions(+), 834 deletions(-) create mode 100644 uSync.Core/Mapping/Tracking/ISyncMapperTracker.cs create mode 100644 uSync.Core/Mapping/Tracking/SyncMapperTrackerCollection.cs delete mode 100644 uSync.Core/Migrations/Cache/ISyncMigratedFullDataSetCachePolicy.cs delete mode 100644 uSync.Core/Migrations/Cache/SyncMigratedFullDataSetCachePolicy.cs delete mode 100644 uSync.Core/Migrations/ISyncMigratedDataRepository.cs delete mode 100644 uSync.Core/Migrations/ISyncMigratedDataService.cs delete mode 100644 uSync.Core/Migrations/Migrations/CreateMigratedDataTable.cs delete mode 100644 uSync.Core/Migrations/Migrations/SyncMigratedDataMigrationPlan.cs delete mode 100644 uSync.Core/Migrations/Notifications/SyncExportCleanNotification.cs delete mode 100644 uSync.Core/Migrations/Notifications/SyncExportCleanNotificationHandler.cs delete mode 100644 uSync.Core/Migrations/SyncMigratedData.cs delete mode 100644 uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs delete mode 100644 uSync.Core/Migrations/SyncMigratedDataRepository.cs delete mode 100644 uSync.Core/Migrations/SyncMigratedDataService.cs delete mode 100644 uSync.Core/Migrations/SyncMigrations.cs create mode 100644 uSync.Core/Notifications/SyncExportCleanNotification.cs delete mode 100644 uSync.Core/Persistance/Cache/ISyncFullDataSetRepositoryCachePolicy.cs delete mode 100644 uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs delete mode 100644 uSync.Core/Persistance/ISyncDataEntity.cs delete mode 100644 uSync.Core/Persistance/ISyncDataRespository.cs delete mode 100644 uSync.Core/Persistance/ISyncDataService.cs delete mode 100644 uSync.Core/Persistance/SyncDataRespositoryBase.cs delete mode 100644 uSync.Core/Persistance/SyncDataServiceBase.cs diff --git a/uSync.BackOffice/Services/SyncService.cs b/uSync.BackOffice/Services/SyncService.cs index 0205bf90..67604ef2 100644 --- a/uSync.BackOffice/Services/SyncService.cs +++ b/uSync.BackOffice/Services/SyncService.cs @@ -1,8 +1,5 @@ using Microsoft.Extensions.Logging; -using Org.BouncyCastle.Asn1.Ocsp; -using Org.BouncyCastle.Tls; - using System; using System.Collections.Generic; using System.Diagnostics; @@ -26,7 +23,7 @@ using uSync.BackOffice.SyncHandlers.Models; using uSync.Core; using uSync.Core.Extensions; -using uSync.Core.Migrations.Notifications; +using uSync.Core.Notifications; using uSync.Core.Serialization; namespace uSync.BackOffice; diff --git a/uSync.Core/DataTypes/ConfigurationSerializerCollectionBuilder.cs b/uSync.Core/DataTypes/ConfigurationSerializerCollectionBuilder.cs index de738376..81509ef6 100644 --- a/uSync.Core/DataTypes/ConfigurationSerializerCollectionBuilder.cs +++ b/uSync.Core/DataTypes/ConfigurationSerializerCollectionBuilder.cs @@ -18,6 +18,7 @@ public ConfigurationSerializerCollection(Func this.FirstOrDefault(x => x.IsSerializer(editorAlias)); @@ -39,4 +40,19 @@ public IEnumerable GetSerializers(string editorAlias) } return null; } + + /// + /// tells serializers that care about it that this is a rename + /// + /// + /// + public async Task TrackRenamedEditorAsync(string oldEditorAlias, string newEditorAlias) { + + foreach(var serializer in GetSerializers(oldEditorAlias)) + { + if (serializer is IConfigurationTrackingSerializer trackingSerializer) + await trackingSerializer.TrackRenamedEditorAsync(oldEditorAlias, newEditorAlias); + } + + } } diff --git a/uSync.Core/DataTypes/IConfigurationSerializer.cs b/uSync.Core/DataTypes/IConfigurationSerializer.cs index f96d1f9d..342c2fec 100644 --- a/uSync.Core/DataTypes/IConfigurationSerializer.cs +++ b/uSync.Core/DataTypes/IConfigurationSerializer.cs @@ -35,3 +35,8 @@ Task> GetConfigurationImportAsync(string name, IDict bool IsSerializer(string propertyName) => Editors.InvariantContains(propertyName); } + +public interface IConfigurationTrackingSerializer : IConfigurationSerializer +{ + Task TrackRenamedEditorAsync(string oldEditorAlias, string newEditorAlias); +} \ No newline at end of file diff --git a/uSync.Core/Mapping/ISyncMapper.cs b/uSync.Core/Mapping/ISyncMapper.cs index 1b73eee6..c90d26b2 100644 --- a/uSync.Core/Mapping/ISyncMapper.cs +++ b/uSync.Core/Mapping/ISyncMapper.cs @@ -9,6 +9,7 @@ public interface ISyncMapper string Name { get; } string[] Editors { get; } + bool IsMapper(string editorAlias); bool IsMapper(PropertyType propertyType); Task GetExportValueAsync(object value, string editorAlias); diff --git a/uSync.Core/Mapping/SyncValueMapperBase.cs b/uSync.Core/Mapping/SyncValueMapperBase.cs index 55baf07f..6521a45f 100644 --- a/uSync.Core/Mapping/SyncValueMapperBase.cs +++ b/uSync.Core/Mapping/SyncValueMapperBase.cs @@ -28,6 +28,9 @@ public SyncValueMapperBase(IEntityService entityService) public abstract string[] Editors { get; } + public virtual bool IsMapper(string editorAlias) + => Editors.InvariantContains(editorAlias); + public virtual bool IsMapper(PropertyType propertyType) => Editors.InvariantContains(propertyType.PropertyEditorAlias); diff --git a/uSync.Core/Mapping/SyncValueMapperCollection.cs b/uSync.Core/Mapping/SyncValueMapperCollection.cs index 8c7453f9..e34d09ff 100644 --- a/uSync.Core/Mapping/SyncValueMapperCollection.cs +++ b/uSync.Core/Mapping/SyncValueMapperCollection.cs @@ -6,29 +6,31 @@ using uSync.Core.Cache; using uSync.Core.Extensions; -using uSync.Core.Migrations; +using uSync.Core.Mapping.Tracking; +using uSync.Core.Tracking; namespace uSync.Core.Mapping; public class SyncValueMapperCollection : BuilderCollectionBase { - private readonly ConcurrentDictionary _customMappings = new(StringComparer.InvariantCultureIgnoreCase); - private readonly ISyncMigratedDataService _migratedDataService; + private readonly SyncMapperTrackerCollection _mapperTrackers; + private readonly ConcurrentDictionary _customMappings = new(StringComparer.InvariantCultureIgnoreCase); + public SyncEntityCache EntityCache { get; private set; } public SyncValueMapperCollection( SyncEntityCache entityCache, Func> items, - ISyncMigratedDataService migratedDataService) + SyncMapperTrackerCollection mapperTrackers) : base(items) { EntityCache = entityCache; // todo, load these from config. _customMappings = []; - _migratedDataService = migratedDataService; + _mapperTrackers = mapperTrackers; } /// @@ -37,21 +39,17 @@ public SyncValueMapperCollection( public IEnumerable GetSyncMappers(string editorAlias) { var mappedAlias = GetMapperAlias(editorAlias); - return this.Where(x => x.Editors.InvariantContains(mappedAlias)); + return this.Where(m => m.IsMapper(editorAlias)); } /// - /// will get any mappers and any mappers associated with the editor alias that have been migrated (if any) + /// will get any mappers and any mappers that are tracked for the editor alias, this is important because /// this allows us to support old mappers for a property editor, even if the property editor alias has changed. /// public async Task> GetImportingSyncMappers(string editorAlias) { - var mappers = new List(); - var importingAlias = await _migratedDataService.GetAsync(editorAlias); - if (importingAlias is not null) - mappers.AddRange(this.Where(x => x.Editors.InvariantContains(importingAlias.Orginal))); - - return [.. mappers, ..GetSyncMappers(editorAlias)]; + var mappers = await _mapperTrackers.GetMappersAsync(editorAlias); + return [.. mappers, .. GetSyncMappers(editorAlias)]; } /// diff --git a/uSync.Core/Mapping/Tracking/ISyncMapperTracker.cs b/uSync.Core/Mapping/Tracking/ISyncMapperTracker.cs new file mode 100644 index 00000000..57a049d9 --- /dev/null +++ b/uSync.Core/Mapping/Tracking/ISyncMapperTracker.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace uSync.Core.Mapping.Tracking; + +/// +/// tracking for when editors might change. +/// +/// +/// A tracker mapper can return additional ISyncMappers for an editor Alias +/// typically we do this when migrations has tracked that a new editorAlias +/// was once an a different editor alias. +/// +public interface ISyncMapperTracker +{ + Task> GetTrackingMappers(string editorAlias); +} diff --git a/uSync.Core/Mapping/Tracking/SyncMapperTrackerCollection.cs b/uSync.Core/Mapping/Tracking/SyncMapperTrackerCollection.cs new file mode 100644 index 00000000..89af6fde --- /dev/null +++ b/uSync.Core/Mapping/Tracking/SyncMapperTrackerCollection.cs @@ -0,0 +1,26 @@ +using Umbraco.Cms.Core.Composing; + +namespace uSync.Core.Mapping.Tracking; + +public class SyncMapperTrackerCollection + : BuilderCollectionBase +{ + public SyncMapperTrackerCollection(Func> items) + : base(items) + { } + + public async Task> GetMappersAsync(string editorAlias) + { + if (this.Count == 0) return Enumerable.Empty(); + + var tasks = this.Select(x => x.GetTrackingMappers(editorAlias)); + var results = await Task.WhenAll(tasks); + return results.SelectMany(x => x); + } +} + +public class SyncMapperTrackerCollectionBuilder + : LazyCollectionBuilderBase +{ + protected override SyncMapperTrackerCollectionBuilder This => this; +} diff --git a/uSync.Core/Migrations/Cache/ISyncMigratedFullDataSetCachePolicy.cs b/uSync.Core/Migrations/Cache/ISyncMigratedFullDataSetCachePolicy.cs deleted file mode 100644 index 9c3b6ca6..00000000 --- a/uSync.Core/Migrations/Cache/ISyncMigratedFullDataSetCachePolicy.cs +++ /dev/null @@ -1,9 +0,0 @@ -using uSync.Core.Persistance.Cache; - -namespace uSync.Core.Migrations.Cache; - -public interface ISyncMigratedFullDataSetCachePolicy - : ISyncFullDataSetRepositoryCachePolicy -{ - -} \ No newline at end of file diff --git a/uSync.Core/Migrations/Cache/SyncMigratedFullDataSetCachePolicy.cs b/uSync.Core/Migrations/Cache/SyncMigratedFullDataSetCachePolicy.cs deleted file mode 100644 index 19c5b51b..00000000 --- a/uSync.Core/Migrations/Cache/SyncMigratedFullDataSetCachePolicy.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Infrastructure.Scoping; - -using uSync.Core.Persistance.Cache; - -namespace uSync.Core.Migrations.Cache; - -internal class SyncMigratedFullDataSetCachePolicy : SyncFullDataSetRepositoryCachePolicy - , ISyncMigratedFullDataSetCachePolicy -{ - public SyncMigratedFullDataSetCachePolicy( - IAppPolicyCache globalCache, - IScopeAccessor scopeAccessor, - IRepositoryCacheVersionService repositoryCacheVersionService, - ICacheSyncService cacheSyncService) - : base(globalCache, scopeAccessor, repositoryCacheVersionService, cacheSyncService) - { } -} diff --git a/uSync.Core/Migrations/ISyncMigratedDataRepository.cs b/uSync.Core/Migrations/ISyncMigratedDataRepository.cs deleted file mode 100644 index c584c3b6..00000000 --- a/uSync.Core/Migrations/ISyncMigratedDataRepository.cs +++ /dev/null @@ -1,6 +0,0 @@ -using uSync.Core.Persistance; - -namespace uSync.Core.Migrations; - -public interface ISyncMigratedDataRepository : ISyncDataRespository -{ } \ No newline at end of file diff --git a/uSync.Core/Migrations/ISyncMigratedDataService.cs b/uSync.Core/Migrations/ISyncMigratedDataService.cs deleted file mode 100644 index 81ecd80f..00000000 --- a/uSync.Core/Migrations/ISyncMigratedDataService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using uSync.Core.Persistance; - -namespace uSync.Core.Migrations; - -public interface ISyncMigratedDataService : ISyncDataService -{ - Task AddRename(string newKey, string oldKey, string? additionalData); - Task DeleteAllAsync(); - Task GetOriginalKeyAsync(string key); -} \ No newline at end of file diff --git a/uSync.Core/Migrations/Migrations/CreateMigratedDataTable.cs b/uSync.Core/Migrations/Migrations/CreateMigratedDataTable.cs deleted file mode 100644 index 476c32c7..00000000 --- a/uSync.Core/Migrations/Migrations/CreateMigratedDataTable.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Umbraco.Cms.Infrastructure.Migrations; - -namespace uSync.Core.Migrations.Migrations; - -internal class CreateMigratedDataTable : AsyncMigrationBase -{ - public CreateMigratedDataTable(IMigrationContext context) : base(context) - { } - - protected override Task MigrateAsync() - { - if (!TableExists(SyncMigrations.MigratedDataTableName)) - Create.Table().Do(); - - return Task.CompletedTask; - } -} diff --git a/uSync.Core/Migrations/Migrations/SyncMigratedDataMigrationPlan.cs b/uSync.Core/Migrations/Migrations/SyncMigratedDataMigrationPlan.cs deleted file mode 100644 index 6370d2cb..00000000 --- a/uSync.Core/Migrations/Migrations/SyncMigratedDataMigrationPlan.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Umbraco.Cms.Infrastructure.Migrations; - -namespace uSync.Core.Migrations.Migrations; - -internal class SyncMigratedDataMigrationPlan : MigrationPlan -{ - public SyncMigratedDataMigrationPlan() - : base(SyncMigrations.AppName) - { - From(string.Empty) - .To("Add SyncMigratedData Table"); - } -} diff --git a/uSync.Core/Migrations/Notifications/SyncExportCleanNotification.cs b/uSync.Core/Migrations/Notifications/SyncExportCleanNotification.cs deleted file mode 100644 index c86ae716..00000000 --- a/uSync.Core/Migrations/Notifications/SyncExportCleanNotification.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -using Umbraco.Cms.Core.Notifications; - -namespace uSync.Core.Migrations.Notifications; - -public class SyncExportCleanNotification : INotification -{ -} diff --git a/uSync.Core/Migrations/Notifications/SyncExportCleanNotificationHandler.cs b/uSync.Core/Migrations/Notifications/SyncExportCleanNotificationHandler.cs deleted file mode 100644 index df7cfb50..00000000 --- a/uSync.Core/Migrations/Notifications/SyncExportCleanNotificationHandler.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Umbraco.Cms.Core.Events; - -namespace uSync.Core.Migrations.Notifications; - -internal class SyncExportCleanNotificationHandler : INotificationAsyncHandler -{ - private readonly ISyncMigratedDataService _syncMigratedDataService; - - public SyncExportCleanNotificationHandler(ISyncMigratedDataService syncMigratedDataService) - { - _syncMigratedDataService = syncMigratedDataService; - } - - public async Task HandleAsync(SyncExportCleanNotification notification, CancellationToken cancellationToken) - { - await _syncMigratedDataService.DeleteAllAsync(); - } -} diff --git a/uSync.Core/Migrations/SyncMigratedData.cs b/uSync.Core/Migrations/SyncMigratedData.cs deleted file mode 100644 index bcb23d1b..00000000 --- a/uSync.Core/Migrations/SyncMigratedData.cs +++ /dev/null @@ -1,32 +0,0 @@ -using NPoco; - -using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; - -using uSync.Core.Persistance; - -namespace uSync.Core.Migrations; - -/// -/// basic information about the migrated item. -/// -[TableName("uSyncMigratedData")] -[PrimaryKey("Id")] -[ExplicitColumns] -public class SyncMigratedData : ISyncDataEntity -{ - [Column("Id")] - [PrimaryKeyColumn] - public int Id { get; set; } - - [Column("Key")] - public required string Key { get; set; } - - [Column("Original")] - public required string Orginal { get; set; } - - [Column("Additional")] - [NullSetting(NullSetting = NullSettings.Null)] - [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] - public string? AdditionalData { get; set; } - -} diff --git a/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs b/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs deleted file mode 100644 index 65bd97ea..00000000 --- a/uSync.Core/Migrations/SyncMigratedDataBuilderExtensions.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -using Umbraco.Cms.Core; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Core.Events; -using Umbraco.Cms.Core.Migrations; -using Umbraco.Cms.Core.Notifications; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Infrastructure.Migrations.Upgrade; - -using uSync.Core.Migrations.Cache; -using uSync.Core.Migrations.Migrations; -using uSync.Core.Migrations.Notifications; - -namespace uSync.Core.Migrations; - -internal static class SyncMigratedDataBuilderExtensions -{ - public static IUmbracoBuilder AddSyncMigratedData(this IUmbracoBuilder builder) - { - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(); - builder.AddNotificationAsyncHandler(); - builder.AddNotificationAsyncHandler(); - - return builder; - } -} - -internal class SyncMigratedDataMigrationHandler : INotificationAsyncHandler -{ - private readonly ICoreScopeProvider _scopeProvider; - private readonly IKeyValueService _keyValueService; - private readonly IRuntimeState _runtimeState; - private readonly IMigrationPlanExecutor _migrationPlanExecutor; - private readonly ILogger _logger; - - public SyncMigratedDataMigrationHandler( - ICoreScopeProvider scopeProvider, - IKeyValueService keyValueService, - IRuntimeState runtimeState, - IMigrationPlanExecutor migrationPlanExecutor, - ILogger logger) - { - _scopeProvider = scopeProvider; - _keyValueService = keyValueService; - _runtimeState = runtimeState; - _migrationPlanExecutor = migrationPlanExecutor; - _logger = logger; - } - - public async Task HandleAsync(UmbracoApplicationStartingNotification notification, CancellationToken cancellationToken) - { - // we don't run our migration until the site has been installed / isn't upgrading. - if (_runtimeState.Level < RuntimeLevel.Run) return; - - // a slightly roundabout way of calling the migration plan - var plan = new SyncMigratedDataMigrationPlan(); - var upgrader = new Upgrader(plan); - - // but here we can pre-check if the migration needs to happen. - // and we reduce the amount of logging that appears at startup if it doesn't. - var currentState = _keyValueService.GetValue(upgrader.StateValueKey); - if (currentState == null || currentState != plan.FinalState) - { - await upgrader.ExecuteAsync(_migrationPlanExecutor, _scopeProvider, _keyValueService); - } - else - { - if (_logger.IsEnabled(LogLevel.Debug)) - _logger.LogDebug("{migration} Migration skipped as it has already been completed in a previous run.", nameof(SyncMigratedDataMigrationPlan)); - } - } -} \ No newline at end of file diff --git a/uSync.Core/Migrations/SyncMigratedDataRepository.cs b/uSync.Core/Migrations/SyncMigratedDataRepository.cs deleted file mode 100644 index b12987b3..00000000 --- a/uSync.Core/Migrations/SyncMigratedDataRepository.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.Extensions.Logging; - -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Infrastructure.Scoping; -using Umbraco.Extensions; - -using uSync.Core.Migrations.Cache; -using uSync.Core.Persistance; - -namespace uSync.Core.Migrations; - -internal class SyncMigratedDataRepository - : SyncDataRespositoryBase, - ISyncMigratedDataRepository -{ - public SyncMigratedDataRepository( - IScopeAccessor scopeAccessor, - AppCaches appCaches, - ISyncMigratedFullDataSetCachePolicy cachePolicy, - ILogger> logger) - : base(scopeAccessor, logger, appCaches, - cachePolicy, SyncMigrations.MigratedDataTableName) - { } - -} diff --git a/uSync.Core/Migrations/SyncMigratedDataService.cs b/uSync.Core/Migrations/SyncMigratedDataService.cs deleted file mode 100644 index ff47cbcd..00000000 --- a/uSync.Core/Migrations/SyncMigratedDataService.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Umbraco.Cms.Core.Scoping; - -using uSync.Core.Persistance; - -namespace uSync.Core.Migrations; - -internal class SyncMigratedDataService : SyncDataServiceBase, - ISyncMigratedDataService -{ - private readonly ISyncMigratedDataRepository _migratedRepository; - - public SyncMigratedDataService( - ISyncMigratedDataRepository migratedRepository, - ICoreScopeProvider scopeProvider) : base(migratedRepository, scopeProvider) - { - _migratedRepository = migratedRepository; - } - - /// - /// tells us if this property has had it's id migrated, - /// - public async Task GetOriginalKeyAsync(string key) - { - var item = await GetAsync(key); - return item?.Orginal ?? key; - } - - public async Task AddRename(string newKey, string oldKey, string? additionalData) - { - var item = new SyncMigratedData - { - Key = newKey, - Orginal = oldKey, - AdditionalData = additionalData - }; - await SaveAsync(item); - } -} - diff --git a/uSync.Core/Migrations/SyncMigrations.cs b/uSync.Core/Migrations/SyncMigrations.cs deleted file mode 100644 index 2f56a735..00000000 --- a/uSync.Core/Migrations/SyncMigrations.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace uSync.Core.Migrations; - -internal static class SyncMigrations -{ - public const string AppName = "SyncMigratedData"; - public const string MigratedDataTableName = "uSyncMigratedData"; -} diff --git a/uSync.Core/Notifications/SyncExportCleanNotification.cs b/uSync.Core/Notifications/SyncExportCleanNotification.cs new file mode 100644 index 00000000..da4f6edf --- /dev/null +++ b/uSync.Core/Notifications/SyncExportCleanNotification.cs @@ -0,0 +1,7 @@ +using Umbraco.Cms.Core.Notifications; + +namespace uSync.Core.Notifications; + +public class SyncExportCleanNotification : INotification +{ +} diff --git a/uSync.Core/Persistance/Cache/ISyncFullDataSetRepositoryCachePolicy.cs b/uSync.Core/Persistance/Cache/ISyncFullDataSetRepositoryCachePolicy.cs deleted file mode 100644 index f899376a..00000000 --- a/uSync.Core/Persistance/Cache/ISyncFullDataSetRepositoryCachePolicy.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace uSync.Core.Persistance.Cache; - -public interface ISyncFullDataSetRepositoryCachePolicy where TModel : class, ISyncDataEntity -{ - void ClearAllAsync(); - Task CreateAsync(TModel model, Func persistNewAsync, CancellationToken cancellationToken = default); - Task DeleteAllAsync(Func persistDeleteAllAsync, CancellationToken cancellationToken = default); - Task DeleteAsync(TModel model, Func persistDeleteAsync, CancellationToken cancellationToken = default); - Task ExistsAsync(TKey key, Func>> performGetAllAsync, CancellationToken cancellationToken = default); - Task GetAllAsync(TKey[]? keys, Func>> performGetAllAsync, CancellationToken cancellationToken = default); - Task GetAsync(TKey key, Func>> performGetAllAsync, CancellationToken cancellationToken = default); - Task UpdateAsync(TModel model, Func persistUpdateAsync, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs b/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs deleted file mode 100644 index ac1d334a..00000000 --- a/uSync.Core/Persistance/Cache/SyncFullDataSetRepositoryCachePolicy.cs +++ /dev/null @@ -1,198 +0,0 @@ -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Scoping; -using Umbraco.Cms.Infrastructure.Scoping; -using Umbraco.Extensions; - -using IScope = Umbraco.Cms.Infrastructure.Scoping.IScope; - -namespace uSync.Core.Persistance.Cache; - -/// -/// this is similar to the SyncDataCachePolicy, except everything is cached in one key. -/// -/// -/// caching all entities works when it is unlikely they will change much during the lookup -/// phase, and there are not a lot (e.g 100+s) of entries, we can cache them, and then -/// all the lookups don't hit the database. -/// -internal class SyncFullDataSetRepositoryCachePolicy - : ISyncFullDataSetRepositoryCachePolicy - where TModel : class, ISyncDataEntity -{ - private const int _cacheDurationMinutes = 10; - - private readonly IAppPolicyCache _globalCache; - private readonly IScopeAccessor _scopeAccessor; - private readonly IRepositoryCacheVersionService _repositoryCacheVersionService; - private readonly ICacheSyncService _cacheSyncService; - - public SyncFullDataSetRepositoryCachePolicy( - IAppPolicyCache globalCache, - IScopeAccessor scopeAccessor, - IRepositoryCacheVersionService repositoryCacheVersionService, - ICacheSyncService cacheSyncService) - { - _globalCache = globalCache; - _scopeAccessor = scopeAccessor; - _repositoryCacheVersionService = repositoryCacheVersionService; - _cacheSyncService = cacheSyncService; - } - - private IAppPolicyCache Cache - { - get - { - IScope? ambientScope = _scopeAccessor.AmbientScope; - return (ambientScope?.RepositoryCacheMode) switch - { - RepositoryCacheMode.Default => _globalCache, - RepositoryCacheMode.Scoped => ambientScope.IsolatedCaches.GetOrCreate(), - RepositoryCacheMode.None => NoAppCache.Instance, - _ => throw new NotSupportedException($"RepositoryCacheMode {ambientScope?.RepositoryCacheMode} is not supported"), - }; - } - } - private string _dataSetCacheKey => $"{typeof(TModel).FullName}_FullDataSet"; - - public void ClearAllAsync() - => Cache.ClearByKey(_dataSetCacheKey); - - public async Task CreateAsync(TModel model, Func persistNewAsync, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(model); - try - { - await persistNewAsync(model); - } - finally - { - ClearAllAsync(); - await RegisterCacheChangeAsync(); - } - } - - public async Task DeleteAsync(TModel model, Func persistDeleteAsync, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(model); - try - { - await persistDeleteAsync(model); - } - finally - { - ClearAllAsync(); - await RegisterCacheChangeAsync(); - } - } - - public async Task DeleteAllAsync(Func persistDeleteAllAsync, CancellationToken cancellationToken = default) - { - try - { - await persistDeleteAllAsync(); - } - finally - { - ClearAllAsync(); - await RegisterCacheChangeAsync(); - } - } - - public async Task UpdateAsync(TModel model, Func persistUpdateAsync, CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(model); - try - { - await persistUpdateAsync(model); - } - finally - { - ClearAllAsync(); - await RegisterCacheChangeAsync(); - } - - } - - public async Task ExistsAsync(TKey key, Func>> performGetAllAsync, CancellationToken cancellationToken = default) - { - await EnsureCacheIsSyncedAsync(); - - var all = await GetAllCached(performGetAllAsync, cancellationToken); - return all.Any(x => x.Key?.Equals(key) is true); - } - - public async Task GetAllAsync(TKey[]? keys, Func>> performGetAllAsync, CancellationToken cancellationToken = default) - { - await EnsureCacheIsSyncedAsync(); - - var all = await GetAllCached(performGetAllAsync, cancellationToken); - if (keys?.Length > 0) - { - return [.. all.Where(x => keys.Contains(x.Key))]; - } - else - { - return all; - } - } - - public async Task GetAsync(TKey key, Func>> performGetAllAsync, CancellationToken cancellationToken = default) - { - await EnsureCacheIsSyncedAsync(); - - var all = await GetAllCached(performGetAllAsync, cancellationToken); - return all.FirstOrDefault(x => x.Key?.Equals(key) is true); - } - - private static readonly SemaphoreSlim _semaphoreLock = new SemaphoreSlim(1,1); - private static readonly TimeSpan _semaphoreLockTimeout = TimeSpan.FromSeconds(90); - - - private async Task GetAllCached(Func>> performGetAllAsync, CancellationToken cancellationToken) - { - var all = Cache.GetCacheItem(_dataSetCacheKey); - if (all is not null) return all; - - if (await _semaphoreLock.WaitAsync(_semaphoreLockTimeout, cancellationToken) is false) - { - // we don't want to cause issues if the cache is missing or contended, - // so avoid blocking and fall back to fetching the data directly without caching. - TModel[] entries = [.. (await performGetAllAsync())]; - return entries; - } - - try - { - // try in the lock, possible something else filled it in while we waited. - all = Cache.GetCacheItem(_dataSetCacheKey); - if (all is not null) return all; - - // go get the data from the database. - TModel[] entries = [.. (await performGetAllAsync())]; - await InsertCacheEntries(entries); - return entries; - } - finally - { - _semaphoreLock.Release(); - } - } - - private Task InsertCacheEntries(TModel[] entries) - { - Cache.Insert(_dataSetCacheKey, () => entries, TimeSpan.FromMinutes(_cacheDurationMinutes), true); - return Task.CompletedTask; - } - - - private async Task EnsureCacheIsSyncedAsync() - { - var synced = await _repositoryCacheVersionService.IsCacheSyncedAsync(); - if (synced) return; - - _cacheSyncService.SyncInternal(CancellationToken.None); - } - - private async Task RegisterCacheChangeAsync() - => await _repositoryCacheVersionService.SetCacheUpdatedAsync(); -} diff --git a/uSync.Core/Persistance/ISyncDataEntity.cs b/uSync.Core/Persistance/ISyncDataEntity.cs deleted file mode 100644 index ed4cf03c..00000000 --- a/uSync.Core/Persistance/ISyncDataEntity.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace uSync.Core.Persistance; - -public interface ISyncDataEntity -{ - int Id { get; set; } - - TId Key { get; } - - bool HasIdentity => Id > 0; -} diff --git a/uSync.Core/Persistance/ISyncDataRespository.cs b/uSync.Core/Persistance/ISyncDataRespository.cs deleted file mode 100644 index 3341cc93..00000000 --- a/uSync.Core/Persistance/ISyncDataRespository.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace uSync.Core.Persistance; - -public interface ISyncDataRespository where TModel : class, ISyncDataEntity -{ - Task CreateAsync(TModel item); - Task DeleteAllAsync(); - Task DeleteAsync(TModel item); - Task ExistsAsync(TKey key); - Task> GetAllAsync(params TKey[] keys); - Task GetAsync(TKey key); - Task UpdateAsync(TModel item); -} \ No newline at end of file diff --git a/uSync.Core/Persistance/ISyncDataService.cs b/uSync.Core/Persistance/ISyncDataService.cs deleted file mode 100644 index d5a94b50..00000000 --- a/uSync.Core/Persistance/ISyncDataService.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace uSync.Core.Persistance; - -public interface ISyncDataService where TModel : class, ISyncDataEntity -{ - Task CreateAsync(TModel item); - Task DeleteAllAsync(); - Task DeleteAsync(TModel item); - Task ExistsAsync(TKey key); - Task> GetAllAsync(params TKey[] keys); - Task GetAsync(TKey key); - Task SaveAsync(TModel item); - Task UpdateAsync(TModel item); -} \ No newline at end of file diff --git a/uSync.Core/Persistance/SyncDataRespositoryBase.cs b/uSync.Core/Persistance/SyncDataRespositoryBase.cs deleted file mode 100644 index 70f05aa6..00000000 --- a/uSync.Core/Persistance/SyncDataRespositoryBase.cs +++ /dev/null @@ -1,187 +0,0 @@ -using Microsoft.Extensions.Logging; - -using NPoco; - -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Infrastructure.Persistence; -using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; -using Umbraco.Cms.Infrastructure.Scoping; -using Umbraco.Extensions; - -using uSync.Core.Persistance.Cache; - -namespace uSync.Core.Persistance; - -/// -/// repository to store some migration info (for when types change from one type to another). -/// -/// -/// we are super cautious with this data, it's not the end of the world if it's not here (for most sites). -/// and this is the only SQL that uSync does anywhere, so we are guarding it, so if it fails we carry on -/// and log the errors so people can see them. -/// - -internal abstract class SyncDataRespositoryBase : ISyncDataRespository - where TModel : class, ISyncDataEntity -{ - protected readonly ISyncFullDataSetRepositoryCachePolicy _cachePolicy; - protected readonly IScopeAccessor _scopeAccessor; - protected readonly AppCaches _appCaches; - protected readonly ILogger> _logger; - - protected readonly string _tableName; - - public SyncDataRespositoryBase( - IScopeAccessor scopeAccessor, - ILogger> logger, - AppCaches appCaches, - ISyncFullDataSetRepositoryCachePolicy cachePolicy, - string tableName) - { - _scopeAccessor = scopeAccessor; - _logger = logger; - _appCaches = appCaches; - _cachePolicy = cachePolicy; - - _tableName = tableName; - } - - protected IScope AmbientScope - { - get - { - - { - var scope = _scopeAccessor.AmbientScope - ?? throw new InvalidOperationException("No ambient scope found"); - return scope; - } - } - } - - protected IUmbracoDatabase Database => AmbientScope.Database; - protected ISqlContext SqlContext => AmbientScope.SqlContext; - protected ISqlSyntaxProvider SqlSyntax => SqlContext.SqlSyntax; - protected Sql Sql() => SqlContext.Sql(); - protected Sql Sql(string sql, params object[] args) - => SqlContext.Sql(sql, args); - - protected virtual Sql GetBaseQuery(bool isCount) - => isCount - ? Sql().SelectCount().From() - : Sql().SelectAll().From(); - - protected virtual string GetBaseWhereClause() - => $"{SqlSyntax.GetQuotedColumnName("Key")} = @Key"; - - protected virtual IEnumerable GetDeleteClauses() - => [$"DELETE FROM {_tableName} WHERE {SqlSyntax.GetQuotedColumnName("Key")} = @Key"]; - - public virtual async Task CreateAsync(TModel item) - => await _cachePolicy.CreateAsync(item, PersistNewItemAsync); - - public virtual async Task UpdateAsync(TModel item) - => await _cachePolicy.UpdateAsync(item, PersistUpdatedItemAsync); - - public virtual async Task DeleteAsync(TModel item) - => await _cachePolicy.DeleteAsync(item, PersistDeletedItemAsync); - - public virtual async Task DeleteAllAsync() - => await _cachePolicy.DeleteAllAsync(PersistDeleteAllAsync); - - public virtual async Task ExistsAsync(Key key) - => await _cachePolicy.ExistsAsync(key, PerformGetAllAsync); - - public virtual async Task GetAsync(Key key) - => await _cachePolicy.GetAsync(key, PerformGetAllAsync); - - public virtual async Task> GetAllAsync(params Key[] keys) - => await _cachePolicy.GetAllAsync(keys, PerformGetAllAsync); - - private async Task PersistNewItemAsync(TModel model) - { - try - { - if (await ExistsAsync(model.Key)) - throw new InvalidOperationException($"An item with the id {model.Key} already exists."); - - using (var transaction = Database.GetTransaction()) - { - await Database.InsertAsync(model); - transaction.Complete(); - } - } - catch(Exception ex) - { - _logger.LogWarning(ex, "uSync Migration - Persist New Item Failed"); - return; - } - } - - private async Task PersistUpdatedItemAsync(TModel model) - { - try - { - if (await ExistsAsync(model.Key) == false) - throw new InvalidOperationException($"An item with the key {model.Key} does not exist."); - - using (var transaction = Database.GetTransaction()) - { - await Database.UpdateAsync(model); - transaction.Complete(); - } - } - catch(Exception ex) - { - _logger.LogWarning(ex, "uSync Migration Update Query Failed"); - return; - } - } - - private async Task PersistDeletedItemAsync(TModel model) - { - try - { - var deletes = GetDeleteClauses(); - foreach (var delete in deletes) - { - await Database.ExecuteAsync(delete, new { model.Key }); - } - } - catch(Exception ex) - { - _logger.LogWarning(ex, "uSync Migration Delete Failed"); - return; - } - } - - private async Task PersistDeleteAllAsync() - { - try - { - var delete = $"DELETE FROM {_tableName}"; - await Database.ExecuteAsync(delete); - } - catch(Exception ex) - { - _logger.LogWarning(ex, "uSync Migration Delete All Failed"); - return; - } - } - - private async Task> PerformGetAllAsync() - { - try - { - var sql = GetBaseQuery(false); - return await Database.FetchAsync(sql); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "uSync Migration Query Failed"); - return []; - } - - } - -} \ No newline at end of file diff --git a/uSync.Core/Persistance/SyncDataServiceBase.cs b/uSync.Core/Persistance/SyncDataServiceBase.cs deleted file mode 100644 index 9beac57e..00000000 --- a/uSync.Core/Persistance/SyncDataServiceBase.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Umbraco.Cms.Core.Scoping; - -namespace uSync.Core.Persistance; - -internal class SyncDataServiceBase : ISyncDataService - where TModel : class, ISyncDataEntity -{ - protected readonly ISyncDataRespository Repository; - protected readonly ICoreScopeProvider ScopeProvider; - - public SyncDataServiceBase( - ISyncDataRespository repository, - ICoreScopeProvider scopeProvider) - { - Repository = repository; - ScopeProvider = scopeProvider; - } - - public virtual async Task CreateAsync(TModel item) - { - using var scope = ScopeProvider.CreateCoreScope(autoComplete: true); - await Repository.CreateAsync(item); - scope.Complete(); - } - - public virtual async Task UpdateAsync(TModel item) - { - using var scope = ScopeProvider.CreateCoreScope(autoComplete: true); - await Repository.UpdateAsync(item); - scope.Complete(); - } - - public virtual async Task SaveAsync(TModel item) - { - var existing = await GetAsync(item.Key); - if (existing != null) - { - item.Id = existing.Id; - await UpdateAsync(item); - } - else - { - await CreateAsync(item); - } - } - - public virtual async Task DeleteAsync(TModel item) - { - using var scope = ScopeProvider.CreateCoreScope(autoComplete: true); - await Repository.DeleteAsync(item); - scope.Complete(); - } - - public virtual async Task DeleteAllAsync() - { - using (var scope = ScopeProvider.CreateCoreScope(autoComplete: true)) - { - await Repository.DeleteAllAsync(); - } - } - - public virtual async Task ExistsAsync(TKey key) - { - using var scope = ScopeProvider.CreateCoreScope(autoComplete: true); - return await Repository.ExistsAsync(key); - } - - public virtual async Task GetAsync(TKey key) - { - using var scope = ScopeProvider.CreateCoreScope(autoComplete: true); - return await Repository.GetAsync(key); - } - - public virtual async Task> GetAllAsync(params TKey[] keys) - { - using var scope = ScopeProvider.CreateCoreScope(autoComplete: true); - return await Repository.GetAllAsync(keys); - } -} diff --git a/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs b/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs index 898cf574..4be86480 100644 --- a/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs +++ b/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs @@ -12,7 +12,6 @@ using uSync.Core.DataTypes; using uSync.Core.Extensions; -using uSync.Core.Migrations; using uSync.Core.Models; namespace uSync.Core.Serialization.Serializers; @@ -26,7 +25,6 @@ public class DataTypeSerializer : SyncContainerSerializerBase, ISyncS private readonly ConfigurationSerializerCollection _configurationSerializers; private readonly PropertyEditorCollection _propertyEditors; private readonly IConfigurationEditorJsonSerializer _jsonSerializer; - private readonly ISyncMigratedDataService _migratedDataService; public DataTypeSerializer(IEntityService entityService, ILogger logger, IDataTypeService dataTypeService, @@ -34,17 +32,15 @@ public DataTypeSerializer(IEntityService entityService, ILogger @@ -120,10 +116,10 @@ protected override async Task> DeserializeCoreAsync(XElem var editor = FindDataEditor(editorAlias); if (editorAlias != item.EditorAlias) { - // change the editor type..... - - // we put this in the migrator service, because it means the value has been migrated. - await _migratedDataService.AddRename(item.EditorAlias, editorAlias, null); + // use the configuration serializers to track the rename. + // uSync.migrations can use this to hook into the rename here, so we don't + // have to track it in the core. + await _configurationSerializers.TrackRenamedEditorAsync(item.EditorAlias, editorAlias); if (editor is not null) { diff --git a/uSync.Core/uSync.Core.csproj b/uSync.Core/uSync.Core.csproj index b4bd0c1e..a11102e6 100644 --- a/uSync.Core/uSync.Core.csproj +++ b/uSync.Core/uSync.Core.csproj @@ -28,4 +28,8 @@ + + + + diff --git a/uSync.Core/uSyncCoreBuilderExtensions.cs b/uSync.Core/uSyncCoreBuilderExtensions.cs index be18bd73..66192bee 100644 --- a/uSync.Core/uSyncCoreBuilderExtensions.cs +++ b/uSync.Core/uSyncCoreBuilderExtensions.cs @@ -8,7 +8,7 @@ using uSync.Core.Dependency; using uSync.Core.Documents; using uSync.Core.Mapping; -using uSync.Core.Migrations; +using uSync.Core.Mapping.Tracking; using uSync.Core.Roots.Configs; using uSync.Core.Serialization; using uSync.Core.Tracking; @@ -35,9 +35,6 @@ public static IUmbracoBuilder AdduSyncCore(this IUmbracoBuilder builder) builder.Services.AddSingleton(); - // migration data, so we can see when things go between types. - builder.AddSyncMigratedData(); - // document url cleaner, for key changes builder.Services.AddSingleton(); @@ -50,10 +47,15 @@ public static IUmbracoBuilder AdduSyncCore(this IUmbracoBuilder builder) builder.WithCollectionBuilder() .Add(builder.TypeLoader.GetTypes()); + // mapping trackers for when editor aliases might have changed + builder.WithCollectionBuilder() + .Add(() => builder.TypeLoader.GetTypes()); + // value mappers, (map internal things in properties in and out of syncing process) builder.WithCollectionBuilder() .Add(builder.TypeLoader.GetTypes()); + // serializers - turn umbraco objects into / from xml in memory. builder.WithCollectionBuilder() .Add(builder.TypeLoader.GetTypes()); From 37312adfb18aa03e12a15daa62c792db0eed2dec Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 26 Mar 2026 11:12:50 +0000 Subject: [PATCH 17/18] Update uSync.Core/Mapping/SyncValueMapperCollection.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- uSync.Core/Mapping/SyncValueMapperCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uSync.Core/Mapping/SyncValueMapperCollection.cs b/uSync.Core/Mapping/SyncValueMapperCollection.cs index e34d09ff..3074e2ea 100644 --- a/uSync.Core/Mapping/SyncValueMapperCollection.cs +++ b/uSync.Core/Mapping/SyncValueMapperCollection.cs @@ -39,7 +39,7 @@ public SyncValueMapperCollection( public IEnumerable GetSyncMappers(string editorAlias) { var mappedAlias = GetMapperAlias(editorAlias); - return this.Where(m => m.IsMapper(editorAlias)); + return this.Where(m => m.IsMapper(mappedAlias)); } /// From 687f55dffa680dd06820a7a51ace7ab526dd7cc6 Mon Sep 17 00:00:00 2001 From: Kevin Jump Date: Thu, 26 Mar 2026 11:15:05 +0000 Subject: [PATCH 18/18] Ensure ISyncMapper new IsMapper method has default implimentation --- uSync.Core/Mapping/ISyncMapper.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/uSync.Core/Mapping/ISyncMapper.cs b/uSync.Core/Mapping/ISyncMapper.cs index c90d26b2..bc79e111 100644 --- a/uSync.Core/Mapping/ISyncMapper.cs +++ b/uSync.Core/Mapping/ISyncMapper.cs @@ -9,7 +9,9 @@ public interface ISyncMapper string Name { get; } string[] Editors { get; } - bool IsMapper(string editorAlias); + bool IsMapper(string editorAlias) + => Editors.Contains(editorAlias, StringComparer.OrdinalIgnoreCase); + bool IsMapper(PropertyType propertyType); Task GetExportValueAsync(object value, string editorAlias);