Performance: Request cache referenced entities when saving documents with block editors (#20590)
* Added request cache to content and media lookups in mult URL picker. * Allow property editors to cache referenced entities from block data. * Update src/Umbraco.Infrastructure/PropertyEditors/MultiUrlPickerValueEditor.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add obsoletions. * Minor spellcheck * Ensure request cache is available before relying on it. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: kjac <kja@umbraco.dk>
This commit is contained in:
@@ -3,12 +3,14 @@ using System.Globalization;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.Editors;
|
||||
using Umbraco.Cms.Core.Models.Validation;
|
||||
using Umbraco.Cms.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Cms.Core.Serialization;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Strings;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
@@ -20,6 +22,9 @@ namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
[DataContract]
|
||||
public class DataValueEditor : IDataValueEditor
|
||||
{
|
||||
private const string ContentCacheKeyFormat = nameof(DataValueEditor) + "_Content_{0}";
|
||||
private const string MediaCacheKeyFormat = nameof(DataValueEditor) + "_Media_{0}";
|
||||
|
||||
private readonly IJsonSerializer? _jsonSerializer;
|
||||
private readonly IShortStringHelper _shortStringHelper;
|
||||
|
||||
@@ -415,4 +420,155 @@ public class DataValueEditor : IDataValueEditor
|
||||
|
||||
return value.TryConvertTo(valueType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a <see cref="IContent"/> instance by its unique identifier, using the provided request cache to avoid redundant
|
||||
/// lookups within the same request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method caches content lookups for the duration of the current request to improve performance when the same content
|
||||
/// item may be accessed multiple times. This is particularly useful in scenarios involving multiple languages or blocks.
|
||||
/// </remarks>
|
||||
/// <param name="key">The unique identifier of the content item to retrieve.</param>
|
||||
/// <param name="requestCache">The request-scoped cache used to store and retrieve content items for the duration of the current request.</param>
|
||||
/// <param name="contentService">The content service used to fetch the content item if it is not found in the cache.</param>
|
||||
/// <returns>The <see cref="IContent"/> instance corresponding to the specified key, or null if no such content item exists.</returns>
|
||||
[Obsolete("This method is available for support of request caching retrieved entities in derived property value editors. " +
|
||||
"The intention is to supersede this with lazy loaded read locks, which will make this unnecessary. " +
|
||||
"Scheduled for removal in Umbraco 19.")]
|
||||
protected static IContent? GetAndCacheContentById(Guid key, IRequestCache requestCache, IContentService contentService)
|
||||
{
|
||||
if (requestCache.IsAvailable is false)
|
||||
{
|
||||
return contentService.GetById(key);
|
||||
}
|
||||
|
||||
var cacheKey = string.Format(ContentCacheKeyFormat, key);
|
||||
IContent? content = requestCache.GetCacheItem<IContent?>(cacheKey);
|
||||
if (content is null)
|
||||
{
|
||||
content = contentService.GetById(key);
|
||||
if (content is not null)
|
||||
{
|
||||
requestCache.Set(cacheKey, content);
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified <see cref="IContent"/> item to the request cache using its unique key.
|
||||
/// </summary>
|
||||
/// <param name="content">The content item to cache.</param>
|
||||
/// <param name="requestCache">The request cache in which to store the content item.</param>
|
||||
[Obsolete("This method is available for support of request caching retrieved entities in derived property value editors. " +
|
||||
"The intention is to supersede this with lazy loaded read locks, which will make this unnecessary. " +
|
||||
"Scheduled for removal in Umbraco 19.")]
|
||||
protected static void CacheContentById(IContent content, IRequestCache requestCache)
|
||||
{
|
||||
if (requestCache.IsAvailable is false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cacheKey = string.Format(ContentCacheKeyFormat, content.Key);
|
||||
requestCache.Set(cacheKey, content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a <see cref="IMedia"/> instance by its unique identifier, using the provided request cache to avoid redundant
|
||||
/// lookups within the same request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method caches media lookups for the duration of the current request to improve performance when the same media
|
||||
/// item may be accessed multiple times. This is particularly useful in scenarios involving multiple languages or blocks.
|
||||
/// </remarks>
|
||||
/// <param name="key">The unique identifier of the media item to retrieve.</param>
|
||||
/// <param name="requestCache">The request-scoped cache used to store and retrieve media items for the duration of the current request.</param>
|
||||
/// <param name="mediaService">The media service used to fetch the media item if it is not found in the cache.</param>
|
||||
/// <returns>The <see cref="IMedia"/> instance corresponding to the specified key, or null if no such media item exists.</returns>
|
||||
[Obsolete("This method is available for support of request caching retrieved entities in derived property value editors. " +
|
||||
"The intention is to supersede this with lazy loaded read locks, which will make this unnecessary. " +
|
||||
"Scheduled for removal in Umbraco 19.")]
|
||||
protected static IMedia? GetAndCacheMediaById(Guid key, IRequestCache requestCache, IMediaService mediaService)
|
||||
{
|
||||
if (requestCache.IsAvailable is false)
|
||||
{
|
||||
return mediaService.GetById(key);
|
||||
}
|
||||
|
||||
var cacheKey = string.Format(MediaCacheKeyFormat, key);
|
||||
IMedia? media = requestCache.GetCacheItem<IMedia?>(cacheKey);
|
||||
|
||||
if (media is null)
|
||||
{
|
||||
media = mediaService.GetById(key);
|
||||
if (media is not null)
|
||||
{
|
||||
requestCache.Set(cacheKey, media);
|
||||
}
|
||||
}
|
||||
|
||||
return media;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified <see cref="IMedia"/> item to the request cache using its unique key.
|
||||
/// </summary>
|
||||
/// <param name="media">The media item to cache.</param>
|
||||
/// <param name="requestCache">The request cache in which to store the media item.</param>
|
||||
[Obsolete("This method is available for support of request caching retrieved entities in derived property value editors. " +
|
||||
"The intention is to supersede this with lazy loaded read locks, which will make this unnecessary. " +
|
||||
"Scheduled for removal in Umbraco 19.")]
|
||||
protected static void CacheMediaById(IMedia media, IRequestCache requestCache)
|
||||
{
|
||||
if (requestCache.IsAvailable is false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cacheKey = string.Format(MediaCacheKeyFormat, media.Key);
|
||||
requestCache.Set(cacheKey, media);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the content item identified by the specified key is present in the request cache.
|
||||
/// </summary>
|
||||
/// <param name="key">The unique identifier for the content item to check for in the cache.</param>
|
||||
/// <param name="requestCache">The request cache in which to look for the content item.</param>
|
||||
/// <returns>true if the content item is already cached in the request cache; otherwise, false.</returns>
|
||||
[Obsolete("This method is available for support of request caching retrieved entities in derived property value editors. " +
|
||||
"The intention is to supersede this with lazy loaded read locks, which will make this unnecessary. " +
|
||||
"Scheduled for removal in Umbraco 19.")]
|
||||
protected static bool IsContentAlreadyCached(Guid key, IRequestCache requestCache)
|
||||
{
|
||||
if (requestCache.IsAvailable is false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var cacheKey = string.Format(ContentCacheKeyFormat, key);
|
||||
return requestCache.GetCacheItem<IContent?>(cacheKey) is not null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the media item identified by the specified key is present in the request cache.
|
||||
/// </summary>
|
||||
/// <param name="key">The unique identifier for the media item to check for in the cache.</param>
|
||||
/// <param name="requestCache">The request cache in which to look for the media item.</param>
|
||||
/// <returns>true if the media item is already cached in the request cache; otherwise, false.</returns>
|
||||
[Obsolete("This method is available for support of request caching retrieved entities in derived property value editors. " +
|
||||
"The intention is to supersede this with lazy loaded read locks, which will make this unnecessary. " +
|
||||
"Scheduled for removal in Umbraco 19.")]
|
||||
protected static bool IsMediaAlreadyCached(Guid key, IRequestCache requestCache)
|
||||
{
|
||||
if (requestCache.IsAvailable is false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var cacheKey = string.Format(MediaCacheKeyFormat, key);
|
||||
return requestCache.GetCacheItem<IMedia?>(cacheKey) is not null;
|
||||
}
|
||||
}
|
||||
|
||||
19
src/Umbraco.Core/PropertyEditors/ICacheReferencedEntities.cs
Normal file
19
src/Umbraco.Core/PropertyEditors/ICacheReferencedEntities.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace Umbraco.Cms.Core.PropertyEditors;
|
||||
|
||||
/// <summary>
|
||||
/// Optionally implemented by property editors, this defines a contract for caching entities that are referenced in block values.
|
||||
/// </summary>
|
||||
[Obsolete("This interface is available for support of request caching retrieved entities in property value editors that implement it. " +
|
||||
"The intention is to supersede this with lazy loaded read locks, which will make this unnecessary. " +
|
||||
"Scheduled for removal in Umbraco 19.")]
|
||||
public interface ICacheReferencedEntities
|
||||
{
|
||||
/// <summary>
|
||||
/// Caches the entities referenced by the provided block data values.
|
||||
/// </summary>
|
||||
/// <param name="values">An enumerable collection of block values that may contain the entities to be cached.</param>
|
||||
[Obsolete("This method is available for support of request caching retrieved entities in derived property value editors. " +
|
||||
"The intention is to supersede this with lazy loaded read locks, which will make this unnecessary. " +
|
||||
"Scheduled for removal in Umbraco 19.")]
|
||||
void CacheReferencedEntities(IEnumerable<object> values);
|
||||
}
|
||||
Reference in New Issue
Block a user