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:
Andy Butland
2020-12-22 11:33:39 +01:00
129 changed files with 3287 additions and 3444 deletions

View File

@@ -1,4 +1,4 @@
using System.Data;
using System.Data;
using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations;

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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>();
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -43,8 +43,6 @@ namespace Umbraco.Infrastructure.Runtime
_databaseFactory = databaseFactory;
_eventAggregator = eventAggregator;
_hostingEnvironment = hostingEnvironment;
_logger = _loggerFactory.CreateLogger<CoreRuntime>();
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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);
}