From 88dd30b6c5cb833edc4782e92ae76226b36e9a37 Mon Sep 17 00:00:00 2001 From: Dirk Seefeld Date: Mon, 15 Sep 2025 13:22:29 +0200 Subject: [PATCH] Repositories: Use NPoco extensions in DatabaseCacheRepository (#19955) * fix sql syntax issues * fix some Copilote comments * fix another Copilot comment removing raw sql strings * fix review comments * Usings and comments. --------- Co-authored-by: Andy Butland --- .../Persistence/DatabaseCacheRepository.cs | 138 +++++++++--------- 1 file changed, 67 insertions(+), 71 deletions(-) diff --git a/src/Umbraco.PublishedCache.HybridCache/Persistence/DatabaseCacheRepository.cs b/src/Umbraco.PublishedCache.HybridCache/Persistence/DatabaseCacheRepository.cs index 6923110a2b..fbdfe33758 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Persistence/DatabaseCacheRepository.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Persistence/DatabaseCacheRepository.cs @@ -14,7 +14,6 @@ using Umbraco.Cms.Infrastructure.HybridCache.Serialization; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement; -using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; using static Umbraco.Cms.Core.Persistence.SqlExtensionsStatics; @@ -61,7 +60,12 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe /// public async Task DeleteContentItemAsync(int id) - => await Database.ExecuteAsync("DELETE FROM cmsContentNu WHERE nodeId = @id", new { id }); + { + Sql sql = Sql() + .Delete() + .Where(x => x.NodeId == id); + await Database.ExecuteAsync(sql); + } /// public async Task RefreshContentAsync(ContentCacheNode contentCacheNode, PublishedState publishedState) @@ -83,7 +87,10 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe await OnRepositoryRefreshed(serializer, contentCacheNode, false); break; case PublishedState.Unpublishing: - await Database.ExecuteAsync("DELETE FROM cmsContentNu WHERE nodeId = @id AND published = 1", new { id = contentCacheNode.Id }); + Sql sql = Sql() + .Delete() + .Where(x => x.NodeId == contentCacheNode.Id && x.Published); + await Database.ExecuteAsync(sql); break; } } @@ -193,11 +200,11 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe ? SqlMediaSourcesSelect() : throw new ArgumentOutOfRangeException(nameof(objectType), objectType, null); - sql.InnerJoin("n") - .On((n, c) => n.NodeId == c.ContentTypeId, "n", "umbracoContent") - .Append(SqlObjectTypeNotTrashed(SqlContext, objectType)) - .WhereIn(x => x.UniqueId, keys,"n") - .Append(SqlOrderByLevelIdSortOrder(SqlContext)); + sql.InnerJoin("n") + .On((n, c) => n.NodeId == c.ContentTypeId, "n", "umbracoContent") + .Append(SqlObjectTypeNotTrashed(SqlContext, objectType)) + .WhereIn(x => x.UniqueId, keys, "n") + .Append(SqlOrderByLevelIdSortOrder(SqlContext)); return GetContentNodeDtos(sql); } @@ -277,12 +284,13 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe private async Task OnRepositoryRefreshed(IContentCacheDataSerializer serializer, ContentCacheNode content, bool preview) { - ContentNuDto dto = GetDtoFromCacheNode(content, !preview, serializer); + string c(string s) => SqlSyntax.GetQuotedColumnName(s); + await Database.InsertOrUpdateAsync( dto, - "SET data = @data, dataRaw = @dataRaw, rv = rv + 1 WHERE nodeId = @id AND published = @published", + $"SET data = @data, {c("dataRaw")} = @dataRaw, rv = rv + 1 WHERE {c("nodeId")} = @id AND published = @published", new { dataRaw = dto.RawData ?? Array.Empty(), @@ -293,7 +301,7 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe } /// - /// Rebuilds the content database cache for documents. + /// Rebuilds the content database cache for documents by clearing and repopulating the cache with the latest document data. /// /// /// Assumes content tree lock. @@ -308,14 +316,7 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe Guid contentObjectType = Constants.ObjectTypes.Document; // Remove all - if anything fails the transaction will rollback. - if (contentTypeIds.Count == 0) - { - DeleteForObjectType(contentObjectType); - } - else - { - DeleteForObjectTypeAndContentTypes(contentObjectType, contentTypeIds); - } + RemoveByObjectType(contentObjectType, contentTypeIds); // Insert back - if anything fails the transaction will rollback. IQuery query = GetInsertQuery(contentTypeIds); @@ -351,10 +352,10 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe } /// - /// Rebuilds the content database cache for media. + /// Rebuilds the content database cache for media by clearing and repopulating the cache with the latest media data. /// /// - /// Assumes media tree lock. + /// Assumes content tree lock. /// private void RebuildMediaDbCache(IContentCacheDataSerializer serializer, int groupSize, IReadOnlyCollection? contentTypeIds) { @@ -366,14 +367,7 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe Guid mediaObjectType = Constants.ObjectTypes.Media; // Remove all - if anything fails the transaction will rollback. - if (contentTypeIds.Count == 0) - { - DeleteForObjectType(mediaObjectType); - } - else - { - DeleteForObjectTypeAndContentTypes(mediaObjectType, contentTypeIds); - } + RemoveByObjectType(mediaObjectType, contentTypeIds); // Insert back - if anything fails the transaction will rollback. IQuery query = GetInsertQuery(contentTypeIds); @@ -394,10 +388,10 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe } /// - /// Rebuilds the content database cache for members. + /// Rebuilds the content database cache for members by clearing and repopulating the cache with the latest member data. /// /// - /// Assumes member tree lock. + /// Assumes content tree lock. /// private void RebuildMemberDbCache(IContentCacheDataSerializer serializer, int groupSize, IReadOnlyCollection? contentTypeIds) { @@ -409,14 +403,7 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe Guid memberObjectType = Constants.ObjectTypes.Member; // Remove all - if anything fails the transaction will rollback. - if (contentTypeIds.Count == 0) - { - DeleteForObjectType(memberObjectType); - } - else - { - DeleteForObjectTypeAndContentTypes(memberObjectType, contentTypeIds); - } + RemoveByObjectType(memberObjectType, contentTypeIds); // Insert back - if anything fails the transaction will rollback. IQuery query = GetInsertQuery(contentTypeIds); @@ -435,26 +422,39 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe while (processed < total); } - private void DeleteForObjectType(Guid nodeObjectType) => - Database.Execute( - @" - DELETE FROM cmsContentNu - WHERE cmsContentNu.nodeId IN ( - SELECT id FROM umbracoNode WHERE umbracoNode.nodeObjectType = @objType - )", - new { objType = nodeObjectType }); - - private void DeleteForObjectTypeAndContentTypes(Guid nodeObjectType, IReadOnlyCollection contentTypeIds) => - Database.Execute( - $@" - DELETE FROM cmsContentNu - WHERE cmsContentNu.nodeId IN ( - SELECT id FROM umbracoNode - JOIN {Constants.DatabaseSchema.Tables.Content} ON {Constants.DatabaseSchema.Tables.Content}.nodeId=umbracoNode.id - WHERE umbracoNode.nodeObjectType = @objType - AND {Constants.DatabaseSchema.Tables.Content}.contentTypeId IN (@ctypes) - )", - new { objType = nodeObjectType, ctypes = contentTypeIds }); + private void RemoveByObjectType(Guid objectType, IReadOnlyCollection contentTypeIds) + { + // If the provided contentTypeIds collection is empty, remove all records for the provided object type. + Sql sql; + if (contentTypeIds.Count == 0) + { + sql = Sql() + .Delete() + .WhereIn( + x => x.NodeId, + Sql().Select(n => n.NodeId) + .From() + .Where(n => n.NodeObjectType == objectType)); + Database.Execute(sql); + } + else + { + // Otherwise, if contentTypeIds are provided remove only those records that match the object type and one of the content types. + sql = Sql() + .Delete() + .WhereIn( + x => x.NodeId, + Sql() + .Select(n => n.NodeId) + .From() + .InnerJoin() + .On((n, c) => n.NodeId == c.NodeId) + .Where((n, c) => + n.NodeObjectType == objectType && + contentTypeIds.Contains(c.ContentTypeId))); + Database.Execute(sql); + } + } private IQuery GetInsertQuery(IReadOnlyCollection contentTypeIds) where TContent : IContentBase @@ -484,7 +484,10 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe var dto = new ContentNuDto { - NodeId = cacheNode.Id, Published = published, Data = serialized.StringData, RawData = serialized.ByteData, + NodeId = cacheNode.Id, + Published = published, + Data = serialized.StringData, + RawData = serialized.ByteData, }; return dto; @@ -560,7 +563,10 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe var dto = new ContentNuDto { - NodeId = content.Id, Published = published, Data = serialized.StringData, RawData = serialized.ByteData, + NodeId = content.Id, + Published = published, + Data = serialized.StringData, + RawData = serialized.ByteData, }; return dto; @@ -633,39 +639,30 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe private Sql SqlWhereNodeKey(ISqlContext sqlContext, Guid key) { - ISqlSyntaxProvider syntax = sqlContext.SqlSyntax; - SqlTemplate sqlTemplate = sqlContext.Templates.Get( Constants.SqlTemplates.NuCacheDatabaseDataSource.WhereNodeKey, builder => builder.Where(x => x.UniqueId == SqlTemplate.Arg("key"))); - Sql sql = sqlTemplate.Sql(key); return sql; } private Sql SqlOrderByLevelIdSortOrder(ISqlContext sqlContext) { - ISqlSyntaxProvider syntax = sqlContext.SqlSyntax; - SqlTemplate sqlTemplate = sqlContext.Templates.Get( Constants.SqlTemplates.NuCacheDatabaseDataSource.OrderByLevelIdSortOrder, s => s.OrderBy(x => x.Level, x => x.ParentId, x => x.SortOrder)); - Sql sql = sqlTemplate.Sql(); return sql; } private Sql SqlObjectTypeNotTrashed(ISqlContext sqlContext, Guid nodeObjectType) { - ISqlSyntaxProvider syntax = sqlContext.SqlSyntax; - SqlTemplate sqlTemplate = sqlContext.Templates.Get( Constants.SqlTemplates.NuCacheDatabaseDataSource.ObjectTypeNotTrashedFilter, s => s.Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType") && x.Trashed == SqlTemplate.Arg("trashed"))); - Sql sql = sqlTemplate.Sql(nodeObjectType, false); return sql; } @@ -681,7 +678,6 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe .From() .InnerJoin().On((left, right) => left.NodeId == right.NodeId) .InnerJoin().On((left, right) => left.NodeId == right.NodeId)); - Sql? sql = sqlTemplate.Sql(); if (joins != null)