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 <abutland73@gmail.com>
This commit is contained in:
Dirk Seefeld
2025-09-15 13:22:29 +02:00
committed by GitHub
parent 84c8c954c7
commit 88dd30b6c5

View File

@@ -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
/// <inheritdoc/>
public async Task DeleteContentItemAsync(int id)
=> await Database.ExecuteAsync("DELETE FROM cmsContentNu WHERE nodeId = @id", new { id });
{
Sql<ISqlContext> sql = Sql()
.Delete<ContentNuDto>()
.Where<ContentNuDto>(x => x.NodeId == id);
await Database.ExecuteAsync(sql);
}
/// <inheritdoc/>
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<ISqlContext> sql = Sql()
.Delete<ContentNuDto>()
.Where<ContentNuDto>(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<NodeDto>("n")
.On<NodeDto, ContentDto>((n, c) => n.NodeId == c.ContentTypeId, "n", "umbracoContent")
.Append(SqlObjectTypeNotTrashed(SqlContext, objectType))
.WhereIn<NodeDto>(x => x.UniqueId, keys,"n")
.Append(SqlOrderByLevelIdSortOrder(SqlContext));
sql.InnerJoin<NodeDto>("n")
.On<NodeDto, ContentDto>((n, c) => n.NodeId == c.ContentTypeId, "n", "umbracoContent")
.Append(SqlObjectTypeNotTrashed(SqlContext, objectType))
.WhereIn<NodeDto>(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<byte>(),
@@ -293,7 +301,7 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// 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<IContent> query = GetInsertQuery<IContent>(contentTypeIds);
@@ -351,10 +352,10 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// Assumes media tree lock.
/// Assumes content tree lock.
/// </remarks>
private void RebuildMediaDbCache(IContentCacheDataSerializer serializer, int groupSize, IReadOnlyCollection<int>? 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<IMedia> query = GetInsertQuery<IMedia>(contentTypeIds);
@@ -394,10 +388,10 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// Assumes member tree lock.
/// Assumes content tree lock.
/// </remarks>
private void RebuildMemberDbCache(IContentCacheDataSerializer serializer, int groupSize, IReadOnlyCollection<int>? 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<IMember> query = GetInsertQuery<IMember>(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<int> 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<int> contentTypeIds)
{
// If the provided contentTypeIds collection is empty, remove all records for the provided object type.
Sql<ISqlContext> sql;
if (contentTypeIds.Count == 0)
{
sql = Sql()
.Delete<ContentNuDto>()
.WhereIn<ContentNuDto>(
x => x.NodeId,
Sql().Select<NodeDto>(n => n.NodeId)
.From<NodeDto>()
.Where<NodeDto>(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<ContentNuDto>()
.WhereIn<ContentNuDto>(
x => x.NodeId,
Sql()
.Select<NodeDto>(n => n.NodeId)
.From<NodeDto>()
.InnerJoin<ContentDto>()
.On<NodeDto, ContentDto>((n, c) => n.NodeId == c.NodeId)
.Where<NodeDto, ContentDto>((n, c) =>
n.NodeObjectType == objectType &&
contentTypeIds.Contains(c.ContentTypeId)));
Database.Execute(sql);
}
}
private IQuery<TContent> GetInsertQuery<TContent>(IReadOnlyCollection<int> 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<ISqlContext> SqlWhereNodeKey(ISqlContext sqlContext, Guid key)
{
ISqlSyntaxProvider syntax = sqlContext.SqlSyntax;
SqlTemplate sqlTemplate = sqlContext.Templates.Get(
Constants.SqlTemplates.NuCacheDatabaseDataSource.WhereNodeKey,
builder =>
builder.Where<NodeDto>(x => x.UniqueId == SqlTemplate.Arg<Guid>("key")));
Sql<ISqlContext> sql = sqlTemplate.Sql(key);
return sql;
}
private Sql<ISqlContext> SqlOrderByLevelIdSortOrder(ISqlContext sqlContext)
{
ISqlSyntaxProvider syntax = sqlContext.SqlSyntax;
SqlTemplate sqlTemplate = sqlContext.Templates.Get(
Constants.SqlTemplates.NuCacheDatabaseDataSource.OrderByLevelIdSortOrder, s =>
s.OrderBy<NodeDto>(x => x.Level, x => x.ParentId, x => x.SortOrder));
Sql<ISqlContext> sql = sqlTemplate.Sql();
return sql;
}
private Sql<ISqlContext> SqlObjectTypeNotTrashed(ISqlContext sqlContext, Guid nodeObjectType)
{
ISqlSyntaxProvider syntax = sqlContext.SqlSyntax;
SqlTemplate sqlTemplate = sqlContext.Templates.Get(
Constants.SqlTemplates.NuCacheDatabaseDataSource.ObjectTypeNotTrashedFilter, s =>
s.Where<NodeDto>(x =>
x.NodeObjectType == SqlTemplate.Arg<Guid?>("nodeObjectType") &&
x.Trashed == SqlTemplate.Arg<bool>("trashed")));
Sql<ISqlContext> sql = sqlTemplate.Sql(nodeObjectType, false);
return sql;
}
@@ -681,7 +678,6 @@ internal sealed class DatabaseCacheRepository : RepositoryBase, IDatabaseCacheRe
.From<NodeDto>()
.InnerJoin<ContentDto>().On<NodeDto, ContentDto>((left, right) => left.NodeId == right.NodeId)
.InnerJoin<DocumentDto>().On<NodeDto, DocumentDto>((left, right) => left.NodeId == right.NodeId));
Sql<ISqlContext>? sql = sqlTemplate.Sql();
if (joins != null)