Merge branch 'netcore/netcore' into feature/clean-up-umbraco-tests-unittests
# Conflicts: # src/Umbraco.Tests.UnitTests/Umbraco.Core/Components/ComponentTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Core/PropertyEditors/BlockListPropertyValueConverterTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/ContentModelBinderTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/ModelBinders/RenderModelBinderTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Web.Common/Views/UmbracoViewPageTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/RenderIndexActionSelectorAttributeTests.cs # src/Umbraco.Tests.UnitTests/Umbraco.Web.Website/Controllers/SurfaceControllerTests.cs
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
using System.Data;
|
||||
using System.Data;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -767,8 +767,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
#region UnitOfWork Events
|
||||
|
||||
// TODO: The reason these events are in the repository is for legacy, the events should exist at the service
|
||||
// level now since we can fire these events within the transaction... so move the events to service level
|
||||
/*
|
||||
* TODO: The reason these events are in the repository is for legacy, the events should exist at the service
|
||||
* level now since we can fire these events within the transaction...
|
||||
* The reason these events 'need' to fire in the transaction is to ensure data consistency with Nucache (currently
|
||||
* the only thing that uses them). For example, if the transaction succeeds and NuCache listened to ContentService.Saved
|
||||
* and then NuCache failed at persisting data after the trans completed, then NuCache would be out of sync. This way
|
||||
* the entire trans is rolled back if NuCache files. This is part of the discussion about removing the static events,
|
||||
* possibly there's 3 levels of eventing, "ing", "scoped" (in trans) and "ed" (after trans).
|
||||
* These particular events can be moved to the service level. However, see the notes below, it seems the only event we
|
||||
* really need is the ScopedEntityRefresh. The only tricky part with moving that to the service level is that the
|
||||
* handlers of that event will need to deal with the data a little differently because it seems that the
|
||||
* "Published" flag on the content item matters and this event is raised before that flag is switched. Weird.
|
||||
* We have the ability with IContent to see if something "WasPublished", etc.. so i think we could still use that.
|
||||
*/
|
||||
|
||||
public class ScopedEntityEventArgs : EventArgs
|
||||
{
|
||||
@@ -784,6 +796,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
public class ScopedVersionEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScopedVersionEventArgs"/> class.
|
||||
/// </summary>
|
||||
public ScopedVersionEventArgs(IScope scope, int entityId, int versionId)
|
||||
{
|
||||
Scope = scope;
|
||||
@@ -791,13 +806,43 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
VersionId = versionId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current <see cref="IScope"/>
|
||||
/// </summary>
|
||||
public IScope Scope { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entity id
|
||||
/// </summary>
|
||||
public int EntityId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version id
|
||||
/// </summary>
|
||||
public int VersionId { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when an <see cref="TEntity"/> is created or updated from within the <see cref="IScope"/> (transaction)
|
||||
/// </summary>
|
||||
public static event TypedEventHandler<TRepository, ScopedEntityEventArgs> ScopedEntityRefresh;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when an <see cref="TEntity"/> is being deleted from within the <see cref="IScope"/> (transaction)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: This doesn't seem to be necessary at all, the service "Deleting" events for this would work just fine
|
||||
/// since they are raised before the item is actually deleted just like this event.
|
||||
/// </remarks>
|
||||
public static event TypedEventHandler<TRepository, ScopedEntityEventArgs> ScopeEntityRemove;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a version for an <see cref="TEntity"/> is being deleted from within the <see cref="IScope"/> (transaction)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: This doesn't seem to be necessary at all, the service "DeletingVersions" events for this would work just fine
|
||||
/// since they are raised before the item is actually deleted just like this event.
|
||||
/// </remarks>
|
||||
public static event TypedEventHandler<TRepository, ScopedVersionEventArgs> ScopeVersionRemove;
|
||||
|
||||
// used by tests to clear events
|
||||
@@ -808,20 +853,23 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
ScopeVersionRemove = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the <see cref="ScopedEntityRefresh"/> event
|
||||
/// </summary>
|
||||
protected void OnUowRefreshedEntity(ScopedEntityEventArgs args)
|
||||
{
|
||||
ScopedEntityRefresh.RaiseEvent(args, This);
|
||||
}
|
||||
=> ScopedEntityRefresh.RaiseEvent(args, This);
|
||||
|
||||
/// <summary>
|
||||
/// Raises the <see cref="ScopeEntityRemove"/> event
|
||||
/// </summary>
|
||||
protected void OnUowRemovingEntity(ScopedEntityEventArgs args)
|
||||
{
|
||||
ScopeEntityRemove.RaiseEvent(args, This);
|
||||
}
|
||||
=> ScopeEntityRemove.RaiseEvent(args, This);
|
||||
|
||||
/// <summary>
|
||||
/// Raises the <see cref="ScopeVersionRemove"/> event
|
||||
/// </summary>
|
||||
protected void OnUowRemovingVersion(ScopedVersionEventArgs args)
|
||||
{
|
||||
ScopeVersionRemove.RaiseEvent(args, This);
|
||||
}
|
||||
=> ScopeVersionRemove.RaiseEvent(args, This);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Scoping;
|
||||
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using static Umbraco.Core.Persistence.SqlExtensionsStatics;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
@@ -20,21 +21,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// <para>Limited to objects that have a corresponding node (in umbracoNode table).</para>
|
||||
/// <para>Returns <see cref="IEntitySlim"/> objects, i.e. lightweight representation of entities.</para>
|
||||
/// </remarks>
|
||||
internal class EntityRepository : IEntityRepository
|
||||
internal class EntityRepository : RepositoryBase, IEntityRepository
|
||||
{
|
||||
private readonly IScopeAccessor _scopeAccessor;
|
||||
|
||||
public EntityRepository(IScopeAccessor scopeAccessor)
|
||||
public EntityRepository(IScopeAccessor scopeAccessor, AppCaches appCaches)
|
||||
: base(scopeAccessor, appCaches)
|
||||
{
|
||||
_scopeAccessor = scopeAccessor;
|
||||
}
|
||||
|
||||
protected IUmbracoDatabase Database => _scopeAccessor.AmbientScope.Database;
|
||||
protected Sql<ISqlContext> Sql() => _scopeAccessor.AmbientScope.SqlContext.Sql();
|
||||
protected ISqlSyntaxProvider SqlSyntax => _scopeAccessor.AmbientScope.SqlContext.SqlSyntax;
|
||||
|
||||
#region Repository
|
||||
|
||||
|
||||
public IEnumerable<IEntitySlim> GetPagedResultsByQuery(IQuery<IUmbracoEntity> query, Guid objectType, long pageIndex, int pageSize, out long totalRecords,
|
||||
IQuery<IUmbracoEntity> filter, Ordering ordering)
|
||||
{
|
||||
@@ -49,17 +44,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var isMedia = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Media);
|
||||
var isMember = objectTypes.Any(objectType => objectType == Constants.ObjectTypes.Member);
|
||||
|
||||
var sql = GetBaseWhere(isContent, isMedia, isMember, false, s =>
|
||||
Sql<ISqlContext> sql = GetBaseWhere(isContent, isMedia, isMember, false, s =>
|
||||
{
|
||||
sqlCustomization?.Invoke(s);
|
||||
|
||||
if (filter != null)
|
||||
{
|
||||
foreach (var filterClause in filter.GetWhereClauses())
|
||||
foreach (Tuple<string, object[]> filterClause in filter.GetWhereClauses())
|
||||
{
|
||||
s.Where(filterClause.Item1, filterClause.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}, objectTypes);
|
||||
|
||||
ordering = ordering ?? Ordering.ByDefault();
|
||||
@@ -75,7 +70,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
|
||||
// TODO: we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently
|
||||
//no matter what we always must have node id ordered at the end
|
||||
// no matter what we always must have node id ordered at the end
|
||||
sql = ordering.Direction == Direction.Ascending ? sql.OrderBy("NodeId") : sql.OrderByDescending("NodeId");
|
||||
|
||||
// for content we must query for ContentEntityDto entities to produce the correct culture variant entity names
|
||||
@@ -102,7 +97,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
private IEntitySlim GetEntity(Sql<ISqlContext> sql, bool isContent, bool isMedia, bool isMember)
|
||||
{
|
||||
//isContent is going to return a 1:M result now with the variants so we need to do different things
|
||||
// isContent is going to return a 1:M result now with the variants so we need to do different things
|
||||
if (isContent)
|
||||
{
|
||||
var cdtos = Database.Fetch<DocumentEntityDto>(sql);
|
||||
@@ -164,7 +159,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
private IEnumerable<IEntitySlim> GetEntities(Sql<ISqlContext> sql, bool isContent, bool isMedia, bool isMember)
|
||||
{
|
||||
//isContent is going to return a 1:M result now with the variants so we need to do different things
|
||||
// isContent is going to return a 1:M result now with the variants so we need to do different things
|
||||
if (isContent)
|
||||
{
|
||||
var cdtos = Database.Fetch<DocumentEntityDto>(sql);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
@@ -9,53 +10,38 @@ using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Provides a base class to all repositories.
|
||||
/// Provides a base class to all <see cref="IEntity"/> based 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>
|
||||
/// <typeparam name="TEntity">The type of the entity managed by this repository.</typeparam>
|
||||
public abstract class EntityRepositoryBase<TId, TEntity> : RepositoryBase, IReadWriteQueryRepository<TId, TEntity>
|
||||
where TEntity : class, IEntity
|
||||
{
|
||||
private IRepositoryCachePolicy<TEntity, TId> _cachePolicy;
|
||||
|
||||
protected RepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger<RepositoryBase<TId, TEntity>> logger)
|
||||
{
|
||||
ScopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor));
|
||||
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
AppCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches));
|
||||
}
|
||||
|
||||
protected ILogger<RepositoryBase<TId, TEntity>> Logger { get; }
|
||||
|
||||
protected AppCaches AppCaches { get; }
|
||||
|
||||
protected IAppPolicyCache GlobalIsolatedCache => AppCaches.IsolatedCaches.GetOrCreate<TEntity>();
|
||||
|
||||
protected IScopeAccessor ScopeAccessor { get; }
|
||||
|
||||
protected IScope AmbientScope
|
||||
{
|
||||
get
|
||||
{
|
||||
var scope = ScopeAccessor.AmbientScope;
|
||||
if (scope == null)
|
||||
throw new InvalidOperationException("Cannot run a repository without an ambient scope.");
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
|
||||
#region Static Queries
|
||||
|
||||
private IQuery<TEntity> _hasIdQuery;
|
||||
private static RepositoryCachePolicyOptions s_defaultOptions;
|
||||
|
||||
#endregion
|
||||
|
||||
protected virtual TId GetEntityId(TEntity entity)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EntityRepositoryBase{TId, TEntity}"/> class.
|
||||
/// </summary>
|
||||
protected EntityRepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches, ILogger<EntityRepositoryBase<TId, TEntity>> logger)
|
||||
: base(scopeAccessor, appCaches)
|
||||
{
|
||||
return (TId) (object) entity.Id;
|
||||
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger
|
||||
/// </summary>
|
||||
protected ILogger<EntityRepositoryBase<TId, TEntity>> Logger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the isolated cache for the <see cref="TEntity"/>
|
||||
/// </summary>
|
||||
protected IAppPolicyCache GlobalIsolatedCache => AppCaches.IsolatedCaches.GetOrCreate<TEntity>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the isolated cache.
|
||||
/// </summary>
|
||||
@@ -78,30 +64,34 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once StaticMemberInGenericType
|
||||
private static RepositoryCachePolicyOptions _defaultOptions;
|
||||
// ReSharper disable once InconsistentNaming
|
||||
protected virtual RepositoryCachePolicyOptions DefaultOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
return _defaultOptions ?? (_defaultOptions
|
||||
/// <summary>
|
||||
/// Gets the default <see cref="RepositoryCachePolicyOptions"/>
|
||||
/// </summary>
|
||||
protected virtual RepositoryCachePolicyOptions DefaultOptions => s_defaultOptions ?? (s_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));
|
||||
IQuery<TEntity> query = _hasIdQuery ?? (_hasIdQuery = AmbientScope.SqlContext.Query<TEntity>().Where(x => x.Id != 0));
|
||||
return PerformCount(query);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the node object type for the repository's entity
|
||||
/// </summary>
|
||||
protected abstract Guid NodeObjectTypeId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the repository cache policy
|
||||
/// </summary>
|
||||
protected IRepositoryCachePolicy<TEntity, TId> CachePolicy
|
||||
{
|
||||
get
|
||||
{
|
||||
if (AppCaches == AppCaches.NoCache)
|
||||
{
|
||||
return NoCacheRepositoryCachePolicy<TEntity, TId>.Instance;
|
||||
}
|
||||
|
||||
// create the cache policy using IsolatedCache which is either global
|
||||
// or scoped depending on the repository cache mode for the current scope
|
||||
@@ -122,66 +112,101 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the entity id for the <see cref="TEntity"/>
|
||||
/// </summary>
|
||||
protected virtual TId GetEntityId(TEntity entity)
|
||||
=> (TId)(object)entity.Id;
|
||||
|
||||
/// <summary>
|
||||
/// Create the repository cache policy
|
||||
/// </summary>
|
||||
protected virtual IRepositoryCachePolicy<TEntity, TId> CreateCachePolicy()
|
||||
{
|
||||
return new DefaultRepositoryCachePolicy<TEntity, TId>(GlobalIsolatedCache, ScopeAccessor, DefaultOptions);
|
||||
}
|
||||
=> new DefaultRepositoryCachePolicy<TEntity, TId>(GlobalIsolatedCache, ScopeAccessor, DefaultOptions);
|
||||
|
||||
/// <summary>
|
||||
/// Adds or Updates an entity of type TEntity
|
||||
/// </summary>
|
||||
/// <remarks>This method is backed by an <see cref="IAppPolicyCache"/> cache</remarks>
|
||||
/// <param name="entity"></param>
|
||||
public virtual void Save(TEntity entity)
|
||||
{
|
||||
if (entity.HasIdentity == false)
|
||||
{
|
||||
CachePolicy.Create(entity, PersistNewItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
CachePolicy.Update(entity, PersistUpdatedItem);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the passed in entity
|
||||
/// </summary>
|
||||
/// <param name="entity"></param>
|
||||
public virtual void Delete(TEntity entity)
|
||||
{
|
||||
CachePolicy.Delete(entity, PersistDeletedItem);
|
||||
}
|
||||
=> CachePolicy.Delete(entity, PersistDeletedItem);
|
||||
|
||||
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);
|
||||
|
||||
protected abstract void PersistNewItem(TEntity item);
|
||||
protected abstract void PersistUpdatedItem(TEntity item);
|
||||
protected abstract void PersistDeletedItem(TEntity item);
|
||||
|
||||
protected abstract void PersistUpdatedItem(TEntity item);
|
||||
|
||||
protected abstract Sql<ISqlContext> GetBaseQuery(bool isCount); // TODO: obsolete, use QueryType instead everywhere
|
||||
|
||||
protected abstract string GetBaseWhereClause();
|
||||
|
||||
protected abstract IEnumerable<string> GetDeleteClauses();
|
||||
|
||||
protected virtual bool PerformExists(TId id)
|
||||
{
|
||||
var sql = GetBaseQuery(true);
|
||||
sql.Where(GetBaseWhereClause(), new { id = id });
|
||||
var count = Database.ExecuteScalar<int>(sql);
|
||||
return count == 1;
|
||||
}
|
||||
|
||||
protected virtual int PerformCount(IQuery<TEntity> query)
|
||||
{
|
||||
var sqlClause = GetBaseQuery(true);
|
||||
var translator = new SqlTranslator<TEntity>(sqlClause, query);
|
||||
var sql = translator.Translate();
|
||||
|
||||
return Database.ExecuteScalar<int>(sql);
|
||||
}
|
||||
|
||||
protected virtual void PersistDeletedItem(TEntity entity)
|
||||
{
|
||||
var deletes = GetDeleteClauses();
|
||||
foreach (var delete in deletes)
|
||||
{
|
||||
Database.Execute(delete, new { id = GetEntityId(entity) });
|
||||
}
|
||||
|
||||
entity.DeleteDate = DateTime.Now;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
=> 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>
|
||||
public IEnumerable<TEntity> GetMany(params TId[] ids)
|
||||
{
|
||||
//ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
|
||||
// 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)
|
||||
|
||||
// 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
|
||||
//.Where(x => Equals(x, default(TId)) == false)
|
||||
// .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,
|
||||
@@ -197,39 +222,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
entities.AddRange(CachePolicy.GetAll(groupOfIds.ToArray(), PerformGetAll));
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of entities by the passed in query
|
||||
/// </summary>
|
||||
/// <param name="query"></param>
|
||||
/// <returns></returns>
|
||||
public IEnumerable<TEntity> Get(IQuery<TEntity> query)
|
||||
{
|
||||
return PerformGetByQuery(query)
|
||||
//ensure we don't include any null refs in the returned collection!
|
||||
.WhereNotNull();
|
||||
}
|
||||
=> PerformGetByQuery(query)
|
||||
.WhereNotNull(); // ensure we don't include any null refs in the returned collection!
|
||||
|
||||
/// <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);
|
||||
}
|
||||
=> 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);
|
||||
}
|
||||
=> PerformCount(query);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NPoco;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
@@ -13,9 +13,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
/// <summary>
|
||||
/// Represent an abstract Repository for NPoco based repositories
|
||||
/// </summary>
|
||||
/// <typeparam name="TId"></typeparam>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
public abstract class NPocoRepositoryBase<TId, TEntity> : RepositoryBase<TId, TEntity>
|
||||
public abstract class NPocoRepositoryBase<TId, TEntity> : EntityRepositoryBase<TId, TEntity>
|
||||
where TEntity : class, IEntity
|
||||
{
|
||||
/// <summary>
|
||||
@@ -24,58 +22,5 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
protected NPocoRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger<NPocoRepositoryBase<TId, TEntity>> logger)
|
||||
: base(scopeAccessor, cache, logger)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the repository's database.
|
||||
/// </summary>
|
||||
protected IUmbracoDatabase Database => AmbientScope.Database;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Sql context.
|
||||
/// </summary>
|
||||
protected ISqlContext SqlContext=> AmbientScope.SqlContext;
|
||||
|
||||
protected Sql<ISqlContext> Sql() => SqlContext.Sql();
|
||||
protected Sql<ISqlContext> Sql(string sql, params object[] args) => SqlContext.Sql(sql, args);
|
||||
protected ISqlSyntaxProvider SqlSyntax => SqlContext.SqlSyntax;
|
||||
protected IQuery<T> Query<T>() => SqlContext.Query<T>();
|
||||
|
||||
#region Abstract Methods
|
||||
|
||||
protected abstract Sql<ISqlContext> GetBaseQuery(bool isCount); // TODO: obsolete, use QueryType instead everywhere
|
||||
protected abstract string GetBaseWhereClause();
|
||||
protected abstract IEnumerable<string> GetDeleteClauses();
|
||||
protected abstract Guid NodeObjectTypeId { get; }
|
||||
protected abstract override void PersistNewItem(TEntity entity);
|
||||
protected abstract override void PersistUpdatedItem(TEntity entity);
|
||||
|
||||
#endregion
|
||||
|
||||
protected override bool PerformExists(TId id)
|
||||
{
|
||||
var sql = GetBaseQuery(true);
|
||||
sql.Where(GetBaseWhereClause(), new { id = id});
|
||||
var count = Database.ExecuteScalar<int>(sql);
|
||||
return count == 1;
|
||||
}
|
||||
|
||||
protected override int PerformCount(IQuery<TEntity> query)
|
||||
{
|
||||
var sqlClause = GetBaseQuery(true);
|
||||
var translator = new SqlTranslator<TEntity>(sqlClause, query);
|
||||
var sql = translator.Translate();
|
||||
|
||||
return Database.ExecuteScalar<int>(sql);
|
||||
}
|
||||
|
||||
protected override void PersistDeletedItem(TEntity entity)
|
||||
{
|
||||
var deletes = GetDeleteClauses();
|
||||
foreach (var delete in deletes)
|
||||
{
|
||||
Database.Execute(delete, new { id = GetEntityId(entity) });
|
||||
}
|
||||
entity.DeleteDate = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using NPoco;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
{
|
||||
/// <summary>
|
||||
/// Base repository class for all <see cref="IRepository"/> instances
|
||||
/// </summary>
|
||||
public abstract class RepositoryBase : IRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RepositoryBase"/> class.
|
||||
/// </summary>
|
||||
protected RepositoryBase(IScopeAccessor scopeAccessor, AppCaches appCaches)
|
||||
{
|
||||
ScopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor));
|
||||
AppCaches = appCaches ?? throw new ArgumentNullException(nameof(appCaches));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="AppCaches"/>
|
||||
/// </summary>
|
||||
protected AppCaches AppCaches { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IScopeAccessor"/>
|
||||
/// </summary>
|
||||
protected IScopeAccessor ScopeAccessor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AmbientScope
|
||||
/// </summary>
|
||||
protected IScope AmbientScope
|
||||
{
|
||||
get
|
||||
{
|
||||
IScope scope = ScopeAccessor.AmbientScope;
|
||||
if (scope == null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot run a repository without an ambient scope.");
|
||||
}
|
||||
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the repository's database.
|
||||
/// </summary>
|
||||
protected IUmbracoDatabase Database => AmbientScope.Database;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Sql context.
|
||||
/// </summary>
|
||||
protected ISqlContext SqlContext => AmbientScope.SqlContext;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ISqlSyntaxProvider"/>
|
||||
/// </summary>
|
||||
protected ISqlSyntaxProvider SqlSyntax => SqlContext.SqlSyntax;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an<see cref="Sql{ISqlContext}"/> expression
|
||||
/// </summary>
|
||||
protected Sql<ISqlContext> Sql() => SqlContext.Sql();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Sql{ISqlContext}"/> expression
|
||||
/// </summary>
|
||||
protected Sql<ISqlContext> Sql(string sql, params object[] args) => SqlContext.Sql(sql, args);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new query expression
|
||||
/// </summary>
|
||||
protected IQuery<T> Query<T>() => SqlContext.Query<T>();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -103,8 +103,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
|
||||
if (settingGuidUdi != null)
|
||||
settingsPublishedElements.TryGetValue(settingGuidUdi.Guid, out settingsData);
|
||||
|
||||
if (!contentData.ContentType.TryGetKey(out var contentTypeKey))
|
||||
throw new InvalidOperationException("The content type was not of type " + typeof(IPublishedContentType2));
|
||||
var contentTypeKey = contentData.ContentType.Key;
|
||||
|
||||
if (!blockConfigMap.TryGetValue(contentTypeKey, out var blockConfig))
|
||||
continue;
|
||||
@@ -113,8 +112,7 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters
|
||||
// we also ensure that the content type's match since maybe the settings type has been changed after this has been persisted.
|
||||
if (settingsData != null)
|
||||
{
|
||||
if (!settingsData.ContentType.TryGetKey(out var settingsElementTypeKey))
|
||||
throw new InvalidOperationException("The settings element type was not of type " + typeof(IPublishedContentType2));
|
||||
var settingsElementTypeKey = settingsData.ContentType.Key;
|
||||
|
||||
if (!blockConfig.SettingsElementTypeKey.HasValue || settingsElementTypeKey != blockConfig.SettingsElementTypeKey)
|
||||
settingsData = null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -190,8 +190,7 @@ namespace Umbraco.Web.PublishedCache
|
||||
try
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
if (type.TryGetKey(out var key))
|
||||
_keyToIdMap[key] = type.Id;
|
||||
_keyToIdMap[type.Key] = type.Id;
|
||||
return _typesByAlias[aliasKey] = _typesById[type.Id] = type;
|
||||
}
|
||||
finally
|
||||
@@ -227,8 +226,7 @@ namespace Umbraco.Web.PublishedCache
|
||||
try
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
if (type.TryGetKey(out var key))
|
||||
_keyToIdMap[key] = type.Id;
|
||||
_keyToIdMap[type.Key] = type.Id;
|
||||
return _typesByAlias[GetAliasKey(type)] = _typesById[type.Id] = type;
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -43,8 +43,6 @@ namespace Umbraco.Infrastructure.Runtime
|
||||
_databaseFactory = databaseFactory;
|
||||
_eventAggregator = eventAggregator;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
|
||||
|
||||
_logger = _loggerFactory.CreateLogger<CoreRuntime>();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Data;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core.Cache;
|
||||
@@ -20,7 +20,6 @@ namespace Umbraco.Core.Scoping
|
||||
private readonly CoreDebugSettings _coreDebugSettings;
|
||||
private readonly IMediaFileSystem _mediaFileSystem;
|
||||
private readonly ILogger<Scope> _logger;
|
||||
private readonly ITypeFinder _typeFinder;
|
||||
|
||||
private readonly IsolationLevel _isolationLevel;
|
||||
private readonly RepositoryCacheMode _repositoryCacheMode;
|
||||
@@ -38,10 +37,15 @@ namespace Umbraco.Core.Scoping
|
||||
private IEventDispatcher _eventDispatcher;
|
||||
|
||||
// initializes a new scope
|
||||
private Scope(ScopeProvider scopeProvider,
|
||||
private Scope(
|
||||
ScopeProvider scopeProvider,
|
||||
CoreDebugSettings coreDebugSettings,
|
||||
IMediaFileSystem mediaFileSystem,
|
||||
ILogger<Scope> logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent, IScopeContext scopeContext, bool detachable,
|
||||
ILogger<Scope> logger,
|
||||
FileSystems fileSystems,
|
||||
Scope parent,
|
||||
IScopeContext scopeContext,
|
||||
bool detachable,
|
||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||
IEventDispatcher eventDispatcher = null,
|
||||
@@ -53,7 +57,6 @@ namespace Umbraco.Core.Scoping
|
||||
_coreDebugSettings = coreDebugSettings;
|
||||
_mediaFileSystem = mediaFileSystem;
|
||||
_logger = logger;
|
||||
_typeFinder = typeFinder;
|
||||
|
||||
Context = scopeContext;
|
||||
|
||||
@@ -117,31 +120,38 @@ namespace Umbraco.Core.Scoping
|
||||
}
|
||||
|
||||
// initializes a new scope
|
||||
public Scope(ScopeProvider scopeProvider,
|
||||
public Scope(
|
||||
ScopeProvider scopeProvider,
|
||||
CoreDebugSettings coreDebugSettings,
|
||||
IMediaFileSystem mediaFileSystem,
|
||||
ILogger<Scope> logger, ITypeFinder typeFinder, FileSystems fileSystems, bool detachable, IScopeContext scopeContext,
|
||||
ILogger<Scope> logger,
|
||||
FileSystems fileSystems,
|
||||
bool detachable,
|
||||
IScopeContext scopeContext,
|
||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||
IEventDispatcher eventDispatcher = null,
|
||||
bool? scopeFileSystems = null,
|
||||
bool callContext = false,
|
||||
bool autoComplete = false)
|
||||
: this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, typeFinder, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete)
|
||||
: this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, fileSystems, null, scopeContext, detachable, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete)
|
||||
{ }
|
||||
|
||||
// initializes a new scope in a nested scopes chain, with its parent
|
||||
public Scope(ScopeProvider scopeProvider,
|
||||
public Scope(
|
||||
ScopeProvider scopeProvider,
|
||||
CoreDebugSettings coreDebugSettings,
|
||||
IMediaFileSystem mediaFileSystem,
|
||||
ILogger<Scope> logger, ITypeFinder typeFinder, FileSystems fileSystems, Scope parent,
|
||||
ILogger<Scope> logger,
|
||||
FileSystems fileSystems,
|
||||
Scope parent,
|
||||
IsolationLevel isolationLevel = IsolationLevel.Unspecified,
|
||||
RepositoryCacheMode repositoryCacheMode = RepositoryCacheMode.Unspecified,
|
||||
IEventDispatcher eventDispatcher = null,
|
||||
bool? scopeFileSystems = null,
|
||||
bool callContext = false,
|
||||
bool autoComplete = false)
|
||||
: this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, typeFinder, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete)
|
||||
: this(scopeProvider, coreDebugSettings, mediaFileSystem, logger, fileSystems, parent, null, false, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete)
|
||||
{ }
|
||||
|
||||
public Guid InstanceId { get; } = Guid.NewGuid();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Data;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -23,13 +23,12 @@ namespace Umbraco.Core.Scoping
|
||||
{
|
||||
private readonly ILogger<ScopeProvider> _logger;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ITypeFinder _typeFinder;
|
||||
private readonly IRequestCache _requestCache;
|
||||
private readonly FileSystems _fileSystems;
|
||||
private readonly CoreDebugSettings _coreDebugSettings;
|
||||
private readonly IMediaFileSystem _mediaFileSystem;
|
||||
|
||||
public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptions<CoreDebugSettings> coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger<ScopeProvider> logger, ILoggerFactory loggerFactory, ITypeFinder typeFinder, IRequestCache requestCache)
|
||||
public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, IOptions<CoreDebugSettings> coreDebugSettings, IMediaFileSystem mediaFileSystem, ILogger<ScopeProvider> logger, ILoggerFactory loggerFactory, IRequestCache requestCache)
|
||||
{
|
||||
DatabaseFactory = databaseFactory;
|
||||
_fileSystems = fileSystems;
|
||||
@@ -37,7 +36,6 @@ namespace Umbraco.Core.Scoping
|
||||
_mediaFileSystem = mediaFileSystem;
|
||||
_logger = logger;
|
||||
_loggerFactory = loggerFactory;
|
||||
_typeFinder = typeFinder;
|
||||
_requestCache = requestCache;
|
||||
// take control of the FileSystems
|
||||
_fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems;
|
||||
@@ -256,7 +254,7 @@ namespace Umbraco.Core.Scoping
|
||||
IEventDispatcher eventDispatcher = null,
|
||||
bool? scopeFileSystems = null)
|
||||
{
|
||||
return new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger<Scope>(), _typeFinder, _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems);
|
||||
return new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger<Scope>(), _fileSystems, true, null, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -312,13 +310,13 @@ namespace Umbraco.Core.Scoping
|
||||
{
|
||||
var ambientContext = AmbientContext;
|
||||
var newContext = ambientContext == null ? new ScopeContext() : null;
|
||||
var scope = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger<Scope>(), _typeFinder, _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete);
|
||||
var scope = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger<Scope>(), _fileSystems, false, newContext, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete);
|
||||
// assign only if scope creation did not throw!
|
||||
SetAmbient(scope, newContext ?? ambientContext);
|
||||
return scope;
|
||||
}
|
||||
|
||||
var nested = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger<Scope>(), _typeFinder, _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete);
|
||||
var nested = new Scope(this, _coreDebugSettings, _mediaFileSystem, _loggerFactory.CreateLogger<Scope>(), _fileSystems, ambientScope, isolationLevel, repositoryCacheMode, eventDispatcher, scopeFileSystems, callContext, autoComplete);
|
||||
SetAmbient(nested, AmbientContext);
|
||||
return nested;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Scoping;
|
||||
@@ -16,10 +16,23 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
protected abstract TService This { get; }
|
||||
|
||||
// that one must be dispatched
|
||||
/// <summary>
|
||||
/// Raised when a <see cref="TItem"/> is changed
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This event is dispatched after the trans is completed. Used by event refreshers.
|
||||
/// </remarks>
|
||||
public static event TypedEventHandler<TService, ContentTypeChange<TItem>.EventArgs> Changed;
|
||||
|
||||
// that one is always immediate (transactional)
|
||||
/// <summary>
|
||||
/// Occurs when an <see cref="TItem"/> is created or updated from within the <see cref="IScope"/> (transaction)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The purpose of this event being raised within the transaction is so that listeners can perform database
|
||||
/// operations from within the same transaction and guarantee data consistency so that if anything goes wrong
|
||||
/// the entire transaction can be rolled back. This is used by Nucache.
|
||||
/// TODO: See remarks in ContentRepositoryBase about these types of events.
|
||||
/// </remarks>
|
||||
public static event TypedEventHandler<TService, ContentTypeChange<TItem>.EventArgs> ScopedRefreshedEntity;
|
||||
|
||||
// used by tests to clear events
|
||||
@@ -45,9 +58,11 @@ namespace Umbraco.Core.Services.Implement
|
||||
scope.Events.Dispatch(Changed, This, args, nameof(Changed));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises the <see cref="ScopedRefreshedEntity"/> event during the <see cref="IScope"/> (transaction)
|
||||
/// </summary>
|
||||
protected void OnUowRefreshedEntity(ContentTypeChange<TItem>.EventArgs args)
|
||||
{
|
||||
// that one is always immediate (not dispatched, transactional)
|
||||
ScopedRefreshedEntity.RaiseEvent(args, This);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user