Files
Umbraco-CMS/src/Umbraco.Infrastructure/Cache/FullDataSetRepositoryCachePolicy.cs
2019-12-18 13:42:27 +01:00

178 lines
6.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Collections;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Scoping;
namespace Umbraco.Core.Cache
{
/// <summary>
/// Represents a caching policy that caches the entire entities set as a single collection.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TId">The type of the identifier.</typeparam>
/// <remarks>
/// <para>Caches the entire set of entities as a single collection.</para>
/// <para>Used by Content-, Media- and MemberTypeRepository, DataTypeRepository, DomainRepository,
/// LanguageRepository, PublicAccessRepository, TemplateRepository... things that make sense to
/// keep as a whole in memory.</para>
/// </remarks>
internal class FullDataSetRepositoryCachePolicy<TEntity, TId> : RepositoryCachePolicyBase<TEntity, TId>
where TEntity : class, IEntity
{
private readonly Func<TEntity, TId> _entityGetId;
private readonly bool _expires;
public FullDataSetRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, Func<TEntity, TId> entityGetId, bool expires)
: base(cache, scopeAccessor)
{
_entityGetId = entityGetId;
_expires = expires;
}
protected static readonly TId[] EmptyIds = new TId[0]; // const
protected string GetEntityTypeCacheKey()
{
return $"uRepo_{typeof (TEntity).Name}_";
}
protected void InsertEntities(TEntity[] entities)
{
// cache is expected to be a deep-cloning cache ie it deep-clones whatever is
// IDeepCloneable when it goes in, and out. it also resets dirty properties,
// making sure that no 'dirty' entity is cached.
//
// this policy is caching the entire list of entities. to ensure that entities
// are properly deep-clones when cached, it uses a DeepCloneableList. however,
// we don't want to deep-clone *each* entity in the list when fetching it from
// cache as that would not be efficient for Get(id). so the DeepCloneableList is
// set to ListCloneBehavior.CloneOnce ie it will clone *once* when inserting,
// and then will *not* clone when retrieving.
var key = GetEntityTypeCacheKey();
if (_expires)
{
Cache.Insert(key, () => new DeepCloneableList<TEntity>(entities), TimeSpan.FromMinutes(5), true);
}
else
{
Cache.Insert(key, () => new DeepCloneableList<TEntity>(entities));
}
}
/// <inheritdoc />
public override void Create(TEntity entity, Action<TEntity> persistNew)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
try
{
persistNew(entity);
}
finally
{
ClearAll();
}
}
/// <inheritdoc />
public override void Update(TEntity entity, Action<TEntity> persistUpdated)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
try
{
persistUpdated(entity);
}
finally
{
ClearAll();
}
}
/// <inheritdoc />
public override void Delete(TEntity entity, Action<TEntity> persistDeleted)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
try
{
persistDeleted(entity);
}
finally
{
ClearAll();
}
}
/// <inheritdoc />
public override TEntity Get(TId id, Func<TId, TEntity> performGet, Func<TId[], IEnumerable<TEntity>> performGetAll)
{
// get all from the cache, then look for the entity
var all = GetAllCached(performGetAll);
var entity = all.FirstOrDefault(x => _entityGetId(x).Equals(id));
// see note in InsertEntities - what we get here is the original
// cached entity, not a clone, so we need to manually ensure it is deep-cloned.
return (TEntity)entity?.DeepClone();
}
/// <inheritdoc />
public override TEntity GetCached(TId id)
{
// get all from the cache -- and only the cache, then look for the entity
var all = Cache.GetCacheItem<DeepCloneableList<TEntity>>(GetEntityTypeCacheKey());
var entity = all?.FirstOrDefault(x => _entityGetId(x).Equals(id));
// see note in InsertEntities - what we get here is the original
// cached entity, not a clone, so we need to manually ensure it is deep-cloned.
return (TEntity) entity?.DeepClone();
}
/// <inheritdoc />
public override bool Exists(TId id, Func<TId, bool> performExits, Func<TId[], IEnumerable<TEntity>> performGetAll)
{
// get all as one set, then look for the entity
var all = GetAllCached(performGetAll);
return all.Any(x => _entityGetId(x).Equals(id));
}
/// <inheritdoc />
public override TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> performGetAll)
{
// get all as one set, from cache if possible, else repo
var all = GetAllCached(performGetAll);
// if ids have been specified, filter
if (ids.Length > 0) all = all.Where(x => ids.Contains(_entityGetId(x)));
// and return
// see note in SetCacheActionToInsertEntities - what we get here is the original
// cached entities, not clones, so we need to manually ensure they are deep-cloned.
return all.Select(x => (TEntity) x.DeepClone()).ToArray();
}
// does NOT clone anything, so be nice with the returned values
internal IEnumerable<TEntity> GetAllCached(Func<TId[], IEnumerable<TEntity>> performGetAll)
{
// try the cache first
var all = Cache.GetCacheItem<DeepCloneableList<TEntity>>(GetEntityTypeCacheKey());
if (all != null) return all.ToArray();
// else get from repo and cache
var entities = performGetAll(EmptyIds).WhereNotNull().ToArray();
InsertEntities(entities); // may be an empty array...
return entities;
}
/// <inheritdoc />
public override void ClearAll()
{
Cache.Clear(GetEntityTypeCacheKey());
}
}
}