U4-8400 - cleanup and document repository cache policies

This commit is contained in:
Stephan
2016-06-01 10:35:44 +02:00
parent 4ba1692b49
commit 743a1451f5
6 changed files with 395 additions and 419 deletions

View File

@@ -1,230 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
/// <summary>
/// The default cache policy for retrieving a single entity
/// Represents the default cache policy.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TId"></typeparam>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TId">The type of the identifier.</typeparam>
/// <remarks>
/// This cache policy uses sliding expiration and caches instances for 5 minutes. However if allow zero count is true, then we use the
/// default policy with no expiry.
/// <para>The default cache policy caches entities with a 5 minutes sliding expiration.</para>
/// <para>Each entity is cached individually.</para>
/// <para>If options.GetAllCacheAllowZeroCount then a 'zero-count' array is cached when GetAll finds nothing.</para>
/// <para>If options.GetAllCacheValidateCount then we check against the db when getting many entities.</para>
/// </remarks>
internal class DefaultRepositoryCachePolicy<TEntity, TId> : RepositoryCachePolicyBase<TEntity, TId>
where TEntity : class, IAggregateRoot
{
private static readonly TEntity[] EmptyEntities = new TEntity[0];
private readonly RepositoryCachePolicyOptions _options;
public DefaultRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options)
: base(cache)
{
if (options == null) throw new ArgumentNullException("options");
_options = options;
{
if (options == null) throw new ArgumentNullException(nameof(options));
_options = options;
}
protected string GetCacheIdKey(object id)
protected string GetEntityCacheKey(object id)
{
if (id == null) throw new ArgumentNullException("id");
return string.Format("{0}{1}", GetCacheTypeKey(), id);
if (id == null) throw new ArgumentNullException(nameof(id));
return GetEntityTypeCacheKey() + id;
}
protected string GetCacheTypeKey()
protected string GetEntityTypeCacheKey()
{
return string.Format("uRepo_{0}_", typeof(TEntity).Name);
return $"uRepo_{typeof (TEntity).Name}_";
}
public override void CreateOrUpdate(TEntity entity, Action<TEntity> persistMethod)
{
if (entity == null) throw new ArgumentNullException("entity");
if (persistMethod == null) throw new ArgumentNullException("persistMethod");
try
{
persistMethod(entity);
//set the disposal action
SetCacheAction(() =>
{
//just to be safe, we cannot cache an item without an identity
if (entity.HasIdentity)
{
Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity,
timeout: TimeSpan.FromMinutes(5),
isSliding: true);
}
//If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.ClearCacheItem(GetCacheTypeKey());
});
}
catch
{
//set the disposal action
SetCacheAction(() =>
{
//if an exception is thrown we need to remove the entry from cache, this is ONLY a work around because of the way
// that we cache entities: http://issues.umbraco.org/issue/U4-4259
Cache.ClearCacheItem(GetCacheIdKey(entity.Id));
//If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.ClearCacheItem(GetCacheTypeKey());
});
throw;
}
}
public override void Remove(TEntity entity, Action<TEntity> persistMethod)
{
if (entity == null) throw new ArgumentNullException("entity");
if (persistMethod == null) throw new ArgumentNullException("persistMethod");
try
{
persistMethod(entity);
}
finally
{
//set the disposal action
var cacheKey = GetCacheIdKey(entity.Id);
SetCacheAction(() =>
{
Cache.ClearCacheItem(cacheKey);
//If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.ClearCacheItem(GetCacheTypeKey());
});
}
}
public override TEntity Get(TId id, Func<TId, TEntity> getFromRepo)
{
if (getFromRepo == null) throw new ArgumentNullException("getFromRepo");
var cacheKey = GetCacheIdKey(id);
var fromCache = Cache.GetCacheItem<TEntity>(cacheKey);
if (fromCache != null)
return fromCache;
var entity = getFromRepo(id);
//set the disposal action
SetCacheAction(cacheKey, entity);
return entity;
}
public override TEntity Get(TId id)
{
var cacheKey = GetCacheIdKey(id);
return Cache.GetCacheItem<TEntity>(cacheKey);
}
public override bool Exists(TId id, Func<TId, bool> getFromRepo)
{
if (getFromRepo == null) throw new ArgumentNullException("getFromRepo");
var cacheKey = GetCacheIdKey(id);
var fromCache = Cache.GetCacheItem<TEntity>(cacheKey);
return fromCache != null || getFromRepo(id);
}
public override TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> getFromRepo)
{
if (getFromRepo == null) throw new ArgumentNullException("getFromRepo");
if (ids.Any())
{
var entities = ids.Select(Get).ToArray();
if (ids.Length.Equals(entities.Length) && entities.Any(x => x == null) == false)
return entities;
}
else
{
var allEntities = GetAllFromCache();
if (allEntities.Any())
{
if (_options.GetAllCacheValidateCount)
{
//Get count of all entities of current type (TEntity) to ensure cached result is correct
var totalCount = _options.PerformCount();
if (allEntities.Length == totalCount)
return allEntities;
}
else
{
return allEntities;
}
}
else if (_options.GetAllCacheAllowZeroCount)
{
//if the repository allows caching a zero count, then check the zero count cache
if (HasZeroCountCache())
{
//there is a zero count cache so return an empty list
return new TEntity[] {};
}
}
}
//we need to do the lookup from the repo
var entityCollection = getFromRepo(ids)
//ensure we don't include any null refs in the returned collection!
.WhereNotNull()
.ToArray();
//set the disposal action
SetCacheAction(ids, entityCollection);
return entityCollection;
}
/// <summary>
/// Looks up the zero count cache, must return null if it doesn't exist
/// </summary>
/// <returns></returns>
protected bool HasZeroCountCache()
{
var zeroCount = Cache.GetCacheItem<TEntity[]>(GetCacheTypeKey());
return (zeroCount != null && zeroCount.Any() == false);
}
/// <summary>
/// Performs the lookup for all entities of this type from the cache
/// </summary>
/// <returns></returns>
protected TEntity[] GetAllFromCache()
{
var allEntities = Cache.GetCacheItemsByKeySearch<TEntity>(GetCacheTypeKey())
.WhereNotNull()
.ToArray();
return allEntities.Any() ? allEntities : new TEntity[] {};
}
/// <summary>
/// Sets the action to execute on disposal for a single entity
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="entity"></param>
protected virtual void SetCacheAction(string cacheKey, TEntity entity)
protected virtual void SetCacheActionToInsertEntity(string cacheKey, TEntity entity)
{
if (entity == null) return;
SetCacheAction(() =>
{
//just to be safe, we cannot cache an item without an identity
if (entity.HasIdentity)
{
Cache.InsertCacheItem(cacheKey, () => entity,
timeout: TimeSpan.FromMinutes(5),
isSliding: true);
}
Cache.InsertCacheItem(cacheKey, () => entity, TimeSpan.FromMinutes(5), true);
});
}
@@ -232,37 +57,185 @@ namespace Umbraco.Core.Cache
/// Sets the action to execute on disposal for an entity collection
/// </summary>
/// <param name="ids"></param>
/// <param name="entityCollection"></param>
protected virtual void SetCacheAction(TId[] ids, TEntity[] entityCollection)
/// <param name="entities"></param>
protected virtual void SetCacheActionToInsertEntities(TId[] ids, TEntity[] entities)
{
SetCacheAction(() =>
{
//This option cannot execute if we are looking up specific Ids
if (ids.Any() == false && entityCollection.Length == 0 && _options.GetAllCacheAllowZeroCount)
if (ids.Length == 0 && entities.Length == 0 && _options.GetAllCacheAllowZeroCount)
{
//there was nothing returned but we want to cache a zero count result so add an TEntity[] to the cache
// to signify that there is a zero count cache
//NOTE: Don't set expiry/sliding for a zero count
Cache.InsertCacheItem(GetCacheTypeKey(), () => new TEntity[] {});
// getting all of them, and finding nothing.
// if we can cache a zero count, cache an empty array,
// for as long as the cache is not cleared (no expiration)
Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => EmptyEntities);
}
else
{
//This is the default behavior, we'll individually cache each item so that if/when these items are resolved
// by id, they are returned from the already existing cache.
foreach (var entity in entityCollection.WhereNotNull())
// individually cache each item
foreach (var entity in entities)
{
var localCopy = entity;
//just to be safe, we cannot cache an item without an identity
if (localCopy.HasIdentity)
{
Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy,
timeout: TimeSpan.FromMinutes(5),
isSliding: true);
}
var capture = entity;
Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => capture, TimeSpan.FromMinutes(5), true);
}
}
});
}
/// <inheritdoc />
public override void CreateOrUpdate(TEntity entity, Action<TEntity> repoCreateOrUpdate)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
if (repoCreateOrUpdate == null) throw new ArgumentNullException(nameof(repoCreateOrUpdate));
try
{
repoCreateOrUpdate(entity);
SetCacheAction(() =>
{
// just to be safe, we cannot cache an item without an identity
if (entity.HasIdentity)
{
Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => entity, TimeSpan.FromMinutes(5), true);
}
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.ClearCacheItem(GetEntityTypeCacheKey());
});
}
catch
{
SetCacheAction(() =>
{
// if an exception is thrown we need to remove the entry from cache,
// this is ONLY a work around because of the way
// that we cache entities: http://issues.umbraco.org/issue/U4-4259
Cache.ClearCacheItem(GetEntityCacheKey(entity.Id));
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.ClearCacheItem(GetEntityTypeCacheKey());
});
throw;
}
}
/// <inheritdoc />
public override void Remove(TEntity entity, Action<TEntity> repoRemove)
{
if (entity == null) throw new ArgumentNullException(nameof(entity));
if (repoRemove == null) throw new ArgumentNullException(nameof(repoRemove));
try
{
repoRemove(entity);
}
finally
{
// whatever happens, clear the cache
var cacheKey = GetEntityCacheKey(entity.Id);
SetCacheAction(() =>
{
Cache.ClearCacheItem(cacheKey);
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.ClearCacheItem(GetEntityTypeCacheKey());
});
}
}
/// <inheritdoc />
public override TEntity Get(TId id, Func<TId, TEntity> repoGet)
{
if (repoGet == null) throw new ArgumentNullException(nameof(repoGet));
var cacheKey = GetEntityCacheKey(id);
var fromCache = Cache.GetCacheItem<TEntity>(cacheKey);
// if found in cache then return else fetch and cache
if (fromCache != null)
return fromCache;
var entity = repoGet(id);
if (entity != null && entity.HasIdentity)
SetCacheActionToInsertEntity(cacheKey, entity);
return entity;
}
/// <inheritdoc />
public override TEntity Get(TId id)
{
var cacheKey = GetEntityCacheKey(id);
return Cache.GetCacheItem<TEntity>(cacheKey);
}
/// <inheritdoc />
public override bool Exists(TId id, Func<TId, bool> repoExists)
{
if (repoExists == null) throw new ArgumentNullException(nameof(repoExists));
// if found in cache the return else check
var cacheKey = GetEntityCacheKey(id);
var fromCache = Cache.GetCacheItem<TEntity>(cacheKey);
return fromCache != null || repoExists(id);
}
/// <inheritdoc />
public override TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> repoGet)
{
if (repoGet == null) throw new ArgumentNullException(nameof(repoGet));
if (ids.Length > 0)
{
// try to get each entity from the cache
// if we can find all of them, return
var entities = ids.Select(Get).ToArray();
if (ids.Length.Equals(entities.Length))
return entities; // no need for null checks, we are not caching nulls
}
else
{
// get everything we have
var entities = Cache.GetCacheItemsByKeySearch<TEntity>(GetEntityTypeCacheKey())
.ToArray(); // no need for null checks, we are not caching nulls
if (entities.Length > 0)
{
// if some of them were in the cache...
if (_options.GetAllCacheValidateCount)
{
// need to validate the count, get the actual count and return if ok
var totalCount = _options.PerformCount();
if (entities.Length == totalCount)
return entities;
}
else
{
// no need to validate, just return what we have and assume it's all there is
return entities;
}
}
else if (_options.GetAllCacheAllowZeroCount)
{
// if none of them were in the cache
// and we allow zero count - check for the special (empty) entry
var empty = Cache.GetCacheItem<TEntity[]>(GetEntityTypeCacheKey());
if (empty != null) return empty;
}
}
// cache failed, get from repo and cache
var repoEntities = repoGet(ids)
.WhereNotNull() // exclude nulls!
.Where(x => x.HasIdentity) // be safe, though would be weird...
.ToArray();
// note: if empty & allow zero count, will cache a special (empty) entry
SetCacheActionToInsertEntities(ids, repoEntities);
return repoEntities;
}
}
}

View File

@@ -7,213 +7,155 @@ using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
/// <summary>
/// A caching policy that caches an entire dataset as a single collection
/// Represents a caching policy that caches the entire entities set as a single collection.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TId"></typeparam>
/// <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, IAggregateRoot
{
private readonly Func<TEntity, TId> _getEntityId;
private readonly Func<IEnumerable<TEntity>> _getAllFromRepo;
private readonly Func<TEntity, TId> _entityGetId;
private readonly Func<IEnumerable<TEntity>> _repoGetAll;
private readonly bool _expires;
public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func<TEntity, TId> getEntityId, Func<IEnumerable<TEntity>> getAllFromRepo, bool expires)
public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func<TEntity, TId> entityGetId, Func<IEnumerable<TEntity>> repoGetAll, bool expires)
: base(cache)
{
_getEntityId = getEntityId;
_getAllFromRepo = getAllFromRepo;
_entityGetId = entityGetId;
_repoGetAll = repoGetAll;
_expires = expires;
}
private bool? _hasZeroCountCache;
protected string GetCacheTypeKey()
protected string GetEntityTypeCacheKey()
{
return string.Format("uRepo_{0}_", typeof(TEntity).Name);
return $"uRepo_{typeof (TEntity).Name}_";
}
public override void CreateOrUpdate(TEntity entity, Action<TEntity> persistMethod)
private void SetCacheActionToClearAll()
{
if (entity == null) throw new ArgumentNullException("entity");
if (persistMethod == null) throw new ArgumentNullException("persistMethod");
try
{
persistMethod(entity);
//set the disposal action
SetCacheAction(() =>
{
//Clear all
Cache.ClearCacheItem(GetCacheTypeKey());
});
}
catch
{
//set the disposal action
SetCacheAction(() =>
{
//Clear all
Cache.ClearCacheItem(GetCacheTypeKey());
});
throw;
}
}
public override void Remove(TEntity entity, Action<TEntity> persistMethod)
{
if (entity == null) throw new ArgumentNullException("entity");
if (persistMethod == null) throw new ArgumentNullException("persistMethod");
try
{
persistMethod(entity);
}
finally
{
//set the disposal action
SetCacheAction(() =>
{
//Clear all
Cache.ClearCacheItem(GetCacheTypeKey());
});
}
}
public override TEntity Get(TId id, Func<TId, TEntity> getFromRepo)
{
//Force get all with cache
var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull());
//we don't have anything in cache (this should never happen), just return from the repo
if (found == null) return getFromRepo(id);
var entity = found.FirstOrDefault(x => _getEntityId(x).Equals(id));
if (entity == null) return null;
//We must ensure to deep clone each one out manually since the deep clone list only clones one way
return (TEntity)entity.DeepClone();
}
public override TEntity Get(TId id)
{
//Force get all with cache
var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull());
//we don't have anything in cache (this should never happen), just return null
if (found == null) return null;
var entity = found.FirstOrDefault(x => _getEntityId(x).Equals(id));
if (entity == null) return null;
//We must ensure to deep clone each one out manually since the deep clone list only clones one way
return (TEntity)entity.DeepClone();
}
public override bool Exists(TId id, Func<TId, bool> getFromRepo)
{
//Force get all with cache
var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull());
//we don't have anything in cache (this should never happen), just return from the repo
return found == null
? getFromRepo(id)
: found.Any(x => _getEntityId(x).Equals(id));
}
public override TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> getFromRepo)
{
//process getting all including setting the cache callback
var result = PerformGetAll(getFromRepo);
//now that the base result has been calculated, they will all be cached.
// Now we can just filter by ids if they have been supplied
return (ids.Any()
? result.Where(x => ids.Contains(_getEntityId(x))).ToArray()
: result)
//We must ensure to deep clone each one out manually since the deep clone list only clones one way
.Select(x => (TEntity)x.DeepClone())
.ToArray();
}
private TEntity[] PerformGetAll(Func<TId[], IEnumerable<TEntity>> getFromRepo)
{
var allEntities = GetAllFromCache();
if (allEntities.Any())
{
return allEntities;
}
//check the zero count cache
if (HasZeroCountCache())
{
//there is a zero count cache so return an empty list
return new TEntity[] { };
}
//we need to do the lookup from the repo
var entityCollection = getFromRepo(new TId[] { })
//ensure we don't include any null refs in the returned collection!
.WhereNotNull()
.ToArray();
//set the disposal action
SetCacheAction(entityCollection);
return entityCollection;
}
/// <summary>
/// Sets the action to execute on disposal for an entity collection
/// </summary>
/// <param name="entityCollection"></param>
protected void SetCacheAction(TEntity[] entityCollection)
{
//set the disposal action
SetCacheAction(() =>
{
//We want to cache the result as a single collection
// clear all, force reload
Cache.ClearCacheItem(GetEntityTypeCacheKey());
});
}
protected void SetCacheActionToInsertEntities(TEntity[] entities)
{
SetCacheAction(() =>
{
// 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.
if (_expires)
{
Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList<TEntity>(entityCollection),
timeout: TimeSpan.FromMinutes(5),
isSliding: true);
Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => new DeepCloneableList<TEntity>(entities), TimeSpan.FromMinutes(5), true);
}
else
{
Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList<TEntity>(entityCollection));
Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => new DeepCloneableList<TEntity>(entities));
}
});
}
/// <summary>
/// Looks up the zero count cache, must return null if it doesn't exist
/// </summary>
/// <returns></returns>
protected bool HasZeroCountCache()
/// <inheritdoc />
public override void CreateOrUpdate(TEntity entity, Action<TEntity> repoCreateOrUpdate)
{
if (_hasZeroCountCache.HasValue)
return _hasZeroCountCache.Value;
if (entity == null) throw new ArgumentNullException(nameof(entity));
if (repoCreateOrUpdate == null) throw new ArgumentNullException(nameof(repoCreateOrUpdate));
_hasZeroCountCache = Cache.GetCacheItem<DeepCloneableList<TEntity>>(GetCacheTypeKey()) != null;
return _hasZeroCountCache.Value;
try
{
repoCreateOrUpdate(entity);
}
finally
{
SetCacheActionToClearAll();
}
}
/// <summary>
/// This policy will cache the full data set as a single collection
/// </summary>
/// <returns></returns>
protected TEntity[] GetAllFromCache()
/// <inheritdoc />
public override void Remove(TEntity entity, Action<TEntity> repoRemove)
{
var found = Cache.GetCacheItem<DeepCloneableList<TEntity>>(GetCacheTypeKey());
if (entity == null) throw new ArgumentNullException(nameof(entity));
if (repoRemove == null) throw new ArgumentNullException(nameof(repoRemove));
//This method will get called before checking for zero count cache, so we'll just set the flag here
_hasZeroCountCache = found != null;
return found == null ? new TEntity[] { } : found.WhereNotNull().ToArray();
try
{
repoRemove(entity);
}
finally
{
SetCacheActionToClearAll();
}
}
/// <inheritdoc />
public override TEntity Get(TId id, Func<TId, TEntity> repoGet)
{
return Get(id);
}
/// <inheritdoc />
public override TEntity Get(TId id)
{
// get all from the cache, the look for the entity
var all = GetAllCached();
var entity = all.FirstOrDefault(x => _entityGetId(x).Equals(id));
// see note in SetCacheActionToInsertEntities - 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> repoExists)
{
// get all as one set, then look for the entity
var all = GetAllCached();
return all.Any(x => _entityGetId(x).Equals(id));
}
/// <inheritdoc />
public override TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> repoGet)
{
// get all as one set, from cache if possible, else repo
var all = GetAllCached();
// 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
private IEnumerable<TEntity> GetAllCached()
{
// 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 = _repoGetAll().WhereNotNull().ToArray();
SetCacheActionToInsertEntities(entities); // may be an empty array...
return entities;
}
}
}

View File

@@ -7,12 +7,55 @@ namespace Umbraco.Core.Cache
internal interface IRepositoryCachePolicy<TEntity, TId> : IDisposable
where TEntity : class, IAggregateRoot
{
TEntity Get(TId id, Func<TId, TEntity> getFromRepo);
/// <summary>
/// Gets an entity from the cache, else from the repository.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="repoGet">The repository method to get the entity.</param>
/// <returns>The entity with the specified identifier, if it exits, else null.</returns>
/// <remarks>First considers the cache then the repository.</remarks>
TEntity Get(TId id, Func<TId, TEntity> repoGet);
/// <summary>
/// Gets an entity from the cache.
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>The entity with the specified identifier, if it is in the cache already, else null.</returns>
/// <remarks>Does not consider the repository at all.</remarks>
TEntity Get(TId id);
bool Exists(TId id, Func<TId, bool> getFromRepo);
void CreateOrUpdate(TEntity entity, Action<TEntity> persistMethod);
void Remove(TEntity entity, Action<TEntity> persistMethod);
TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> getFromRepo);
/// <summary>
/// Gets a value indicating whether an entity with a specified identifier exists.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="repoExists">The repository method to check for the existence of the entity.</param>
/// <returns>A value indicating whether an entity with the specified identifier exists.</returns>
/// <remarks>First considers the cache then the repository.</remarks>
bool Exists(TId id, Func<TId, bool> repoExists);
/// <summary>
/// Creates or updates an entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="repoCreateOrUpdate">The repository method to create or update the entity.</param>
/// <remarks>Creates or updates the entity in the repository, and updates the cache accordingly.</remarks>
void CreateOrUpdate(TEntity entity, Action<TEntity> repoCreateOrUpdate);
/// <summary>
/// Removes an entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <param name="repoRemove">The repository method to remove the entity.</param>
/// <remarks>Removes the entity from the repository and clears the cache.</remarks>
void Remove(TEntity entity, Action<TEntity> repoRemove);
/// <summary>
/// Gets entities.
/// </summary>
/// <param name="ids">The identifiers.</param>
/// <param name="repoGet">The repository method to get entities.</param>
/// <returns>If <paramref name="ids"/> is empty, all entities, else the entities with the specified identifiers.</returns>
/// <remarks>fixme explain what it should do!</remarks>
TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> repoGet);
}
}

View File

@@ -4,6 +4,11 @@ using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
/// <summary>
/// A base class for repository cache policies.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TId">The type of the identifier.</typeparam>
internal abstract class RepositoryCachePolicyBase<TEntity, TId> : DisposableObject, IRepositoryCachePolicy<TEntity, TId>
where TEntity : class, IAggregateRoot
{
@@ -11,38 +16,45 @@ namespace Umbraco.Core.Cache
protected RepositoryCachePolicyBase(IRuntimeCacheProvider cache)
{
if (cache == null) throw new ArgumentNullException("cache");
if (cache == null) throw new ArgumentNullException(nameof(cache));
Cache = cache;
}
protected IRuntimeCacheProvider Cache { get; private set; }
protected IRuntimeCacheProvider Cache { get; }
/// <summary>
/// The disposal performs the caching
/// Disposing performs the actual caching action.
/// </summary>
protected override void DisposeResources()
{
if (_action != null)
{
_action();
}
_action?.Invoke();
}
/// <summary>
/// Sets the action to execute on disposal
/// Sets the action to execute when being disposed.
/// </summary>
/// <param name="action"></param>
/// <param name="action">An action to perform when being disposed.</param>
protected void SetCacheAction(Action action)
{
_action = action;
}
public abstract TEntity Get(TId id, Func<TId, TEntity> getFromRepo);
/// <inheritdoc />
public abstract TEntity Get(TId id, Func<TId, TEntity> repoGet);
/// <inheritdoc />
public abstract TEntity Get(TId id);
public abstract bool Exists(TId id, Func<TId, bool> getFromRepo);
public abstract void CreateOrUpdate(TEntity entity, Action<TEntity> persistMethod);
public abstract void Remove(TEntity entity, Action<TEntity> persistMethod);
public abstract TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> getFromRepo);
/// <inheritdoc />
public abstract bool Exists(TId id, Func<TId, bool> repoExists);
/// <inheritdoc />
public abstract void CreateOrUpdate(TEntity entity, Action<TEntity> repoCreateOrUpdate);
/// <inheritdoc />
public abstract void Remove(TEntity entity, Action<TEntity> repoRemove);
/// <inheritdoc />
public abstract TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> repoGet);
}
}

View File

@@ -2,6 +2,9 @@ using System;
namespace Umbraco.Core.Cache
{
/// <summary>
/// Specifies how a repository cache policy should cache entities.
/// </summary>
internal class RepositoryCachePolicyOptions
{
/// <summary>

View File

@@ -1,24 +1,27 @@
using System.Linq;
using Umbraco.Core.Collections;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
/// <summary>
/// A caching policy that ignores all caches for GetAll - it will only cache calls for individual items
/// Represents a special policy that does not cache the result of GetAll.
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TId"></typeparam>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TId">The type of the identifier.</typeparam>
/// <remarks>
/// <para>Overrides the default repository cache policy and does not writes the result of GetAll
/// to cache, but only the result of individual Gets. It does read the cache for GetAll, though.</para>
/// <para>Used by DictionaryRepository.</para>
/// </remarks>
internal class SingleItemsOnlyRepositoryCachePolicy<TEntity, TId> : DefaultRepositoryCachePolicy<TEntity, TId>
where TEntity : class, IAggregateRoot
{
public SingleItemsOnlyRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) : base(cache, options)
public SingleItemsOnlyRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options)
: base(cache, options)
{ }
protected override void SetCacheActionToInsertEntities(TId[] ids, TEntity[] entities)
{
}
protected override void SetCacheAction(TId[] ids, TEntity[] entityCollection)
{
//no-op
// nop
}
}
}