Files
Umbraco-CMS/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/RepositoryBaseOfTIdTEntity.cs

236 lines
9.0 KiB
C#
Raw Normal View History

2017-07-20 11:21:28 +02:00
using System;
2017-05-12 14:49:44 +02:00
using System.Collections.Generic;
using System.Linq;
2020-09-17 09:42:55 +02:00
using Microsoft.Extensions.Logging;
2017-05-12 14:49:44 +02:00
using Umbraco.Core.Cache;
using Umbraco.Core.Models.Entities;
2017-05-12 14:49:44 +02:00
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
2017-12-07 16:45:25 +01:00
namespace Umbraco.Core.Persistence.Repositories.Implement
2017-05-12 14:49:44 +02:00
{
/// <summary>
/// Provides a base class to all repositories.
/// </summary>
/// <typeparam name="TEntity">The type of the entity managed by this repository.</typeparam>
/// <typeparam name="TId">The type of the entity's unique identifier.</typeparam>
public abstract class RepositoryBase<TId, TEntity> : IReadWriteQueryRepository<TId, TEntity>
where TEntity : class, IEntity
2017-05-12 14:49:44 +02:00
{
private IRepositoryCachePolicy<TEntity, TId> _cachePolicy;
2020-09-17 09:42:55 +02:00
protected RepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger<RepositoryBase<TId, TEntity>> logger)
2017-12-07 16:45:25 +01:00
{
2017-12-14 17:04:44 +01:00
ScopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor));
2017-12-07 16:45:25 +01:00
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
AppCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches));
2017-12-07 16:45:25 +01:00
}
2020-09-17 09:42:55 +02:00
protected ILogger<RepositoryBase<TId, TEntity>> Logger { get; }
2017-12-07 16:45:25 +01:00
protected AppCaches AppCaches { get; }
2017-12-07 16:45:25 +01:00
2019-01-18 07:56:38 +01:00
protected IAppPolicyCache GlobalIsolatedCache => AppCaches.IsolatedCaches.GetOrCreate<TEntity>();
2017-12-14 17:04:44 +01:00
protected IScopeAccessor ScopeAccessor { get; }
2017-12-12 15:04:13 +01:00
protected IScope AmbientScope
{
get
{
2017-12-14 17:04:44 +01:00
var scope = ScopeAccessor.AmbientScope;
2017-12-12 15:04:13 +01:00
if (scope == null)
throw new InvalidOperationException("Cannot run a repository without an ambient scope.");
return scope;
}
}
2017-05-12 14:49:44 +02:00
#region Static Queries
private IQuery<TEntity> _hasIdQuery;
#endregion
protected virtual TId GetEntityId(TEntity entity)
{
return (TId) (object) entity.Id;
}
/// <summary>
/// Gets the isolated cache.
2017-05-12 14:49:44 +02:00
/// </summary>
/// <remarks>Depends on the ambient scope cache mode.</remarks>
2019-01-18 07:56:38 +01:00
protected IAppPolicyCache IsolatedCache
2017-05-12 14:49:44 +02:00
{
get
{
2017-12-12 15:04:13 +01:00
switch (AmbientScope.RepositoryCacheMode)
2017-05-12 14:49:44 +02:00
{
case RepositoryCacheMode.Default:
return AppCaches.IsolatedCaches.GetOrCreate<TEntity>();
2017-05-12 14:49:44 +02:00
case RepositoryCacheMode.Scoped:
2019-01-17 11:01:23 +01:00
return AmbientScope.IsolatedCaches.GetOrCreate<TEntity>();
2017-06-23 18:54:42 +02:00
case RepositoryCacheMode.None:
2019-01-17 11:01:23 +01:00
return NoAppCache.Instance;
2017-05-12 14:49:44 +02:00
default:
throw new Exception("oops: cache mode.");
}
}
}
// ReSharper disable once StaticMemberInGenericType
private static RepositoryCachePolicyOptions _defaultOptions;
// ReSharper disable once InconsistentNaming
2017-05-12 14:49:44 +02:00
protected virtual RepositoryCachePolicyOptions DefaultOptions
{
get
{
return _defaultOptions ?? (_defaultOptions
= new RepositoryCachePolicyOptions(() =>
{
// get count of all entities of current type (TEntity) to ensure cached result is correct
// create query once if it is needed (no need for locking here) - query is static!
var query = _hasIdQuery ?? (_hasIdQuery = AmbientScope.SqlContext.Query<TEntity>().Where(x => x.Id != 0));
return PerformCount(query);
}));
2017-05-12 14:49:44 +02:00
}
}
protected IRepositoryCachePolicy<TEntity, TId> CachePolicy
2017-05-12 14:49:44 +02:00
{
get
{
if (AppCaches == AppCaches.NoCache)
return NoCacheRepositoryCachePolicy<TEntity, TId>.Instance;
2017-05-12 14:49:44 +02:00
// create the cache policy using IsolatedCache which is either global
2017-07-20 11:21:28 +02:00
// or scoped depending on the repository cache mode for the current scope
2017-12-12 15:04:13 +01:00
switch (AmbientScope.RepositoryCacheMode)
2017-05-12 14:49:44 +02:00
{
case RepositoryCacheMode.Default:
case RepositoryCacheMode.Scoped:
// return the same cache policy in both cases - the cache policy is
// supposed to pick either the global or scope cache depending on the
// scope cache mode
return _cachePolicy ?? (_cachePolicy = CreateCachePolicy());
2017-06-23 18:54:42 +02:00
case RepositoryCacheMode.None:
return NoCacheRepositoryCachePolicy<TEntity, TId>.Instance;
2017-05-12 14:49:44 +02:00
default:
throw new Exception("oops: cache mode.");
}
}
}
protected virtual IRepositoryCachePolicy<TEntity, TId> CreateCachePolicy()
2017-05-12 14:49:44 +02:00
{
return new DefaultRepositoryCachePolicy<TEntity, TId>(GlobalIsolatedCache, ScopeAccessor, DefaultOptions);
2017-05-12 14:49:44 +02:00
}
/// <summary>
/// Adds or Updates an entity of type TEntity
/// </summary>
2019-01-18 07:56:38 +01:00
/// <remarks>This method is backed by an <see cref="IAppPolicyCache"/> cache</remarks>
2017-05-12 14:49:44 +02:00
/// <param name="entity"></param>
public virtual void Save(TEntity entity)
2017-05-12 14:49:44 +02:00
{
if (entity.HasIdentity == false)
CachePolicy.Create(entity, PersistNewItem);
2017-05-12 14:49:44 +02:00
else
CachePolicy.Update(entity, PersistUpdatedItem);
2017-05-12 14:49:44 +02:00
}
/// <summary>
/// Deletes the passed in entity
/// </summary>
/// <param name="entity"></param>
public virtual void Delete(TEntity entity)
{
CachePolicy.Delete(entity, PersistDeletedItem);
2017-05-12 14:49:44 +02:00
}
protected abstract TEntity PerformGet(TId id);
protected abstract IEnumerable<TEntity> PerformGetAll(params TId[] ids);
protected abstract IEnumerable<TEntity> PerformGetByQuery(IQuery<TEntity> query);
protected abstract bool PerformExists(TId id);
protected abstract int PerformCount(IQuery<TEntity> query);
2017-05-12 14:49:44 +02:00
protected abstract void PersistNewItem(TEntity item);
protected abstract void PersistUpdatedItem(TEntity item);
protected abstract void PersistDeletedItem(TEntity item);
/// <summary>
/// Gets an entity by the passed in Id utilizing the repository's cache policy
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public TEntity Get(TId id)
{
return CachePolicy.Get(id, PerformGet, PerformGetAll);
}
/// <summary>
/// Gets all entities of type TEntity or a list according to the passed in Ids
/// </summary>
/// <param name="ids"></param>
/// <returns></returns>
2017-12-07 16:45:25 +01:00
public IEnumerable<TEntity> GetMany(params TId[] ids)
2017-05-12 14:49:44 +02:00
{
//ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
ids = ids.Distinct()
//don't query by anything that is a default of T (like a zero)
// TODO: I think we should enabled this in case accidental calls are made to get all with invalid ids
2017-05-12 14:49:44 +02:00
//.Where(x => Equals(x, default(TId)) == false)
.ToArray();
// can't query more than 2000 ids at a time... but if someone is really querying 2000+ entities,
// the additional overhead of fetching them in groups is minimal compared to the lookup time of each group
const int maxParams = 2000;
if (ids.Length <= maxParams)
2017-05-12 14:49:44 +02:00
{
return CachePolicy.GetAll(ids, PerformGetAll);
2017-05-12 14:49:44 +02:00
}
var entities = new List<TEntity>();
foreach (var groupOfIds in ids.InGroupsOf(maxParams))
{
entities.AddRange(CachePolicy.GetAll(groupOfIds.ToArray(), PerformGetAll));
}
return entities;
2017-05-12 14:49:44 +02:00
}
/// <summary>
/// Gets a list of entities by the passed in query
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
2017-12-07 16:45:25 +01:00
public IEnumerable<TEntity> Get(IQuery<TEntity> query)
2017-05-12 14:49:44 +02:00
{
return PerformGetByQuery(query)
//ensure we don't include any null refs in the returned collection!
.WhereNotNull();
}
/// <summary>
/// Returns a boolean indicating whether an entity with the passed Id exists
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public bool Exists(TId id)
{
return CachePolicy.Exists(id, PerformExists, PerformGetAll);
}
/// <summary>
/// Returns an integer with the count of entities found with the passed in query
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
public int Count(IQuery<TEntity> query)
{
return PerformCount(query);
}
}
2017-07-20 11:21:28 +02:00
}