U4-9322 - backport repository cache policy changes from v8

This commit is contained in:
Stephan
2017-01-18 18:48:13 +01:00
parent a020a02b26
commit 4ab7f768fa
25 changed files with 544 additions and 722 deletions

View File

@@ -7,223 +7,168 @@ 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 bool _expires;
public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func<TEntity, TId> getEntityId, Func<IEnumerable<TEntity>> getAllFromRepo, bool expires)
public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func<TEntity, TId> entityGetId, bool expires)
: base(cache)
{
_getEntityId = getEntityId;
_getAllFromRepo = getAllFromRepo;
_entityGetId = entityGetId;
_expires = expires;
}
private bool? _hasZeroCountCache;
protected static readonly TId[] EmptyIds = new TId[0]; // const
protected string GetCacheTypeKey()
protected string GetEntityTypeCacheKey()
{
return string.Format("uRepo_{0}_", typeof(TEntity).Name);
}
public override void CreateOrUpdate(TEntity entity, Action<TEntity> persistMethod)
protected void InsertEntities(TEntity[] entities)
{
if (entity == null) throw new ArgumentNullException("entity");
if (persistMethod == null) throw new ArgumentNullException("persistMethod");
// 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.
try
if (_expires)
{
persistMethod(entity);
//set the disposal action
SetCacheAction(() =>
{
//Clear all
Cache.ClearCacheItem(GetCacheTypeKey());
});
Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => new DeepCloneableList<TEntity>(entities), TimeSpan.FromMinutes(5), true);
}
catch
else
{
//set the disposal action
SetCacheAction(() =>
{
//Clear all
Cache.ClearCacheItem(GetCacheTypeKey());
});
throw;
Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => new DeepCloneableList<TEntity>(entities));
}
}
public override void Remove(TEntity entity, Action<TEntity> persistMethod)
/// <inheritdoc />
public override void Create(TEntity entity, Action<TEntity> persistNew)
{
if (entity == null) throw new ArgumentNullException("entity");
if (persistMethod == null) throw new ArgumentNullException("persistMethod");
try
{
persistMethod(entity);
persistNew(entity);
}
finally
{
//set the disposal action
SetCacheAction(() =>
{
//Clear all
Cache.ClearCacheItem(GetCacheTypeKey());
});
ClearAll();
}
}
public override TEntity Get(TId id, Func<TId, TEntity> getFromRepo)
/// <inheritdoc />
public override void Update(TEntity entity, Action<TEntity> persistUpdated)
{
//Force get all with cache
var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull());
if (entity == null) throw new ArgumentNullException("entity");
//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())
try
{
return allEntities;
persistUpdated(entity);
}
//check the zero count cache
if (HasZeroCountCache())
finally
{
//there is a zero count cache so return an empty list
return new TEntity[] { };
ClearAll();
}
//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>
/// For this type of caching policy, we don't cache individual items
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="entity"></param>
protected void SetCacheAction(string cacheKey, TEntity entity)
/// <inheritdoc />
public override void Delete(TEntity entity, Action<TEntity> persistDeleted)
{
//No-op
}
if (entity == null) throw new ArgumentNullException("entity");
/// <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(() =>
try
{
//We want to cache the result as a single collection
if (_expires)
{
Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList<TEntity>(entityCollection),
timeout: TimeSpan.FromMinutes(5),
isSliding: true);
}
else
{
Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList<TEntity>(entityCollection));
}
});
persistDeleted(entity);
}
finally
{
ClearAll();
}
}
/// <summary>
/// Looks up the zero count cache, must return null if it doesn't exist
/// </summary>
/// <returns></returns>
protected bool HasZeroCountCache()
/// <inheritdoc />
public override TEntity Get(TId id, Func<TId, TEntity> performGet, Func<TId[], IEnumerable<TEntity>> performGetAll)
{
if (_hasZeroCountCache.HasValue)
return _hasZeroCountCache.Value;
// get all from the cache, then look for the entity
var all = GetAllCached(performGetAll);
var entity = all.FirstOrDefault(x => _entityGetId(x).Equals(id));
_hasZeroCountCache = Cache.GetCacheItem<DeepCloneableList<TEntity>>(GetCacheTypeKey()) != null;
return _hasZeroCountCache.Value;
// 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 entity == null ? null : (TEntity) entity.DeepClone();
}
/// <summary>
/// This policy will cache the full data set as a single collection
/// </summary>
/// <returns></returns>
protected TEntity[] GetAllFromCache()
/// <inheritdoc />
public override TEntity GetCached(TId id)
{
var found = Cache.GetCacheItem<DeepCloneableList<TEntity>>(GetCacheTypeKey());
// get all from the cache -- and only the cache, then look for the entity
var all = Cache.GetCacheItem<DeepCloneableList<TEntity>>(GetEntityTypeCacheKey());
var entity = all == null ? null : all.FirstOrDefault(x => _entityGetId(x).Equals(id));
//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();
// 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 entity == null ? null : (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
private 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.ClearCacheItem(GetEntityTypeCacheKey());
}
}
}