Merge branch 'temp8' into temp8-dirty-tracking-on-variants

This commit is contained in:
Stephan
2018-10-23 10:51:28 +02:00
50 changed files with 1110 additions and 981 deletions

View File

@@ -20,7 +20,8 @@ namespace Umbraco.Core.Manifest
// show: [ // optional, default is always show
// '-content/foo', // hide for content type 'foo'
// '+content/*', // show for all other content types
// '+media/*' // show for all media types
// '+media/*', // show for all media types
// '+role/admin' // show for admin users. Role based permissions will override others.
// ]
// },
// ...

View File

@@ -73,7 +73,7 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> Where<TDto>(this Sql<ISqlContext> sql, Expression<Func<TDto, bool>> predicate, string alias = null)
{
var (s, a) = sql.SqlContext.Visit(predicate, alias);
var (s, a) = sql.SqlContext.VisitDto(predicate, alias);
return sql.Where(s, a);
}
@@ -89,7 +89,7 @@ namespace Umbraco.Core.Persistence
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> Where<TDto1, TDto2>(this Sql<ISqlContext> sql, Expression<Func<TDto1, TDto2, bool>> predicate, string alias1 = null, string alias2 = null)
{
var (s, a) = sql.SqlContext.Visit(predicate, alias1, alias2);
var (s, a) = sql.SqlContext.VisitDto(predicate, alias1, alias2);
return sql.Where(s, a);
}
@@ -321,9 +321,9 @@ namespace Umbraco.Core.Persistence
/// Appends an ORDER BY DESC clause to the Sql statement.
/// </summary>
/// <param name="sql">The Sql statement.</param>
/// <param name="fields">Expression specifying the fields.</param>
/// <param name="fields">Fields.</param>
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> OrderByDescending(this Sql<ISqlContext> sql, params object[] fields)
public static Sql<ISqlContext> OrderByDescending(this Sql<ISqlContext> sql, params string[] fields)
{
return sql.Append("ORDER BY " + string.Join(", ", fields.Select(x => x + " DESC")));
}
@@ -664,7 +664,7 @@ namespace Umbraco.Core.Persistence
/// Adds columns to a SELECT Sql statement.
/// </summary>
/// <param name="sql">The origin sql.</param>
/// <param name="fields">Expression indicating the column to select.</param>
/// <param name="fields">Columns to select.</param>
/// <returns>The Sql statement.</returns>
public static Sql<ISqlContext> AndSelect(this Sql<ISqlContext> sql, params string[] fields)
{
@@ -688,7 +688,6 @@ namespace Umbraco.Core.Persistence
return sql.Append(", " + string.Join(", ", sql.GetColumns(columnExpressions: fields)));
}
/// <summary>
/// Adds columns to a SELECT Sql statement.
/// </summary>

View File

@@ -19,6 +19,12 @@ namespace Umbraco.Core.Persistence.Repositories
/// <remarks>Current version is first, and then versions are ordered with most recent first.</remarks>
IEnumerable<TEntity> GetAllVersions(int nodeId);
/// <summary>
/// Gets versions.
/// </summary>
/// <remarks>Current version is first, and then versions are ordered with most recent first.</remarks>
IEnumerable<TEntity> GetAllVersionsSlim(int nodeId, int skip, int take);
/// <summary>
/// Gets version identifiers.
/// </summary>

View File

@@ -53,6 +53,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// gets all versions, current first
public abstract IEnumerable<TEntity> GetAllVersions(int nodeId);
// gets all versions, current first
public virtual IEnumerable<TEntity> GetAllVersionsSlim(int nodeId, int skip, int take)
=> GetAllVersions(nodeId).Skip(skip).Take(take);
// gets all version ids, current first
public virtual IEnumerable<int> GetVersionIds(int nodeId, int maxRows)
{
@@ -252,8 +256,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column
// is empty for many nodes) - see: http://issues.umbraco.org/issue/U4-8831
var dbfield = GetQuotedFieldName("umbracoNode", "id");
(dbfield, _) = SqlContext.Visit<NodeDto>(x => x.NodeId); // fixme?!
var (dbfield, _) = SqlContext.VisitDto<NodeDto>(x => x.NodeId);
if (ordering.IsCustomField || !ordering.OrderBy.InvariantEquals("id"))
{
psql.OrderBy(GetAliasedField(dbfield, sql)); // fixme why aliased?
@@ -262,7 +265,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// create prepared sql
// ensure it's single-line as NPoco PagingHelper has issues with multi-lines
psql = Sql(psql.SQL.ToSingleLine(), psql.Arguments);
// replace the magic culture parameter (see DocumentRepository.GetBaseQuery())
if (!ordering.Culture.IsNullOrWhiteSpace())
@@ -353,6 +355,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (ordering.Culture.IsNullOrWhiteSpace())
return GetAliasedField(SqlSyntax.GetFieldName<NodeDto>(x => x.Text), sql);
// "variantName" alias is defined in DocumentRepository.GetBaseQuery
// fixme - what if it is NOT a document but a ... media or whatever?
// previously, we inserted the join+select *here* so we were sure to have it,
// but now that's not the case anymore!
return "variantName";
}
@@ -433,7 +439,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// sort and filter
sql = PreparePageSql(sql, filter, ordering);
// get a page of DTOs and the total count
var pagedResult = Database.Page<TDto>(pageIndex + 1, pageSize, sql);
totalRecords = Convert.ToInt32(pagedResult.TotalItems);

View File

@@ -95,6 +95,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return GetBaseQuery(queryType, true);
}
// gets the COALESCE expression for variant/invariant name
private string VariantNameSqlExpression
=> SqlContext.VisitDto<ContentVersionCultureVariationDto, NodeDto>((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql;
protected virtual Sql<ISqlContext> GetBaseQuery(QueryType queryType, bool current)
{
var sql = SqlContext.Sql();
@@ -116,7 +120,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto))
.Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 =>
r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv")))
.AndSelect(SqlContext.Visit<ContentVersionCultureVariationDto, NodeDto>((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + " AS variantName");
// select the variant name, coalesce to the invariant name, as "variantName"
.AndSelect(VariantNameSqlExpression + " AS variantName");
break;
}
@@ -135,18 +141,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
.LeftJoin<ContentVersionDto>(nested =>
nested.InnerJoin<DocumentVersionDto>("pdv")
.On<ContentVersionDto, DocumentVersionDto>((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv")
.On<DocumentDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv");
.On<DocumentDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv")
//the magic [[[ISOCODE]]] will be replaced in ContentRepositoryBase.GetPage() by the current Iso code
sql
// left join on optional culture variation
//the magic "[[[ISOCODE]]]" parameter value will be replaced in ContentRepositoryBase.GetPage() by the actual ISO code
.LeftJoin<ContentVersionCultureVariationDto>(nested =>
nested.InnerJoin<LanguageDto>("lang").On<ContentVersionCultureVariationDto, LanguageDto>((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv")
.On<ContentVersionDto, ContentVersionCultureVariationDto>((version, ccv) => version.Id == ccv.VersionId, "pcv", "ccv");
.On<ContentVersionDto, ContentVersionCultureVariationDto>((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv");
sql
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
// this would ensure we don't get the published version - keep for reference
//sql
// .WhereAny(
@@ -157,7 +162,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (current)
sql.Where<ContentVersionDto>(x => x.Current); // always get the current version
return sql;
}
@@ -216,6 +220,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return MapDtosToContent(Database.Fetch<DocumentDto>(sql), true);
}
public override IEnumerable<IContent> GetAllVersionsSlim(int nodeId, int skip, int take)
{
var sql = GetBaseQuery(QueryType.Many, false)
.Where<NodeDto>(x => x.NodeId == nodeId)
.OrderByDescending<ContentVersionDto>(x => x.Current)
.AndByDescending<ContentVersionDto>(x => x.VersionDate);
return MapDtosToContent(Database.Fetch<DocumentDto>(sql), true, true);
}
public override IContent GetVersion(int versionId)
{
var sql = GetBaseQuery(QueryType.Single, false)
@@ -246,7 +260,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// however, it's not just so we have access to AddingEntity
// there are tons of things at the end of the methods, that can only work with a true Content
// and basically, the repository requires a Content, not an IContent
var content = (Content)entity;
var content = (Content) entity;
content.AddingEntity();
var publishing = content.PublishedState == PublishedState.Publishing;
@@ -420,7 +434,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// however, it's not just so we have access to AddingEntity
// there are tons of things at the end of the methods, that can only work with a true Content
// and basically, the repository requires a Content, not an IContent
var content = (Content)entity;
var content = (Content) entity;
// check if we need to make any database changes at all
if ((content.PublishedState == PublishedState.Published || content.PublishedState == PublishedState.Unpublished) && !content.IsEntityDirty() && !content.IsAnyUserPropertyDirty())
@@ -707,30 +721,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
Sql<ISqlContext> filterSql = null;
// Here we create a default where clause from a temp IContent which will look in the contentVersion table for the content name
// if we are searching in a list view that contains variants, we want to look in the contentVersionCultureVariation table instead.
// The resulting clause will be used in the foreach below to compare against the original clause that comes from the "filter" and if they are the same
// we know that we are searching a list view and the proper where clause will be replaced to look in contentVersionCultureVariation table for the names.
var temp = Query<IContent>().Where(x => x.Name.Contains("foo"));
var clause = temp.GetWhereClauses().First().Item1.Split(' ')[0];
// if we have a filter, map its clauses to an Sql statement
if (filter != null)
{
// if the clause works on "name", we need to swap the field and use the variantName instead,
// so that querying also works on variant content (for instance when searching a listview).
// figure out how the "name" field is going to look like - so we can look for it
var nameField = SqlContext.VisitModelField<IContent>(x => x.Name);
filterSql = Sql();
foreach (var filterClause in filter.GetWhereClauses())
{
// fixme - is this the right way of doing it???
var clauseSql = filterClause.Item1;
var clauseArgs = filterClause.Item2;
//
var where = filterClause.Item1.Split(' ')[0] == clause
// normally, this would be the field alias (variantName) of the coalesce result between ContentVersionCulture and NodeDto names, however
// you can't refer to field alias in a WHERE clause so we have to put the coalesce calculation instead which refers to the original field
? SqlContext.Visit<ContentVersionCultureVariationDto, NodeDto>((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql
: filterClause.Item1;
// replace the name field
// we cannot reference an aliased field in a WHERE clause, so have to repeat the expression here
clauseSql = clauseSql.Replace(nameField, VariantNameSqlExpression);
filterSql.Append(
where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)" : $"AND ({where})",
filterClause.Item2);
// append the clause
filterSql.Append($"AND ({clauseSql})", clauseArgs);
}
}
@@ -910,7 +921,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return base.ApplySystemOrdering(ref sql, ordering);
}
private IEnumerable<IContent> MapDtosToContent(List<DocumentDto> dtos, bool withCache = false)
private IEnumerable<IContent> MapDtosToContent(List<DocumentDto> dtos, bool withCache = false, bool slim = false)
{
var temps = new List<TempContent<Content>>();
var contentTypes = new Dictionary<int, IContentType>();
@@ -928,7 +939,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var cached = IsolatedCache.GetCacheItem<IContent>(RepositoryCacheKeys.GetKey<IContent>(dto.NodeId));
if (cached != null && cached.VersionId == dto.DocumentVersionDto.ContentVersionDto.Id)
{
content[i] = (Content)cached;
content[i] = (Content) cached;
continue;
}
}
@@ -943,18 +954,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var c = content[i] = ContentBaseFactory.BuildEntity(dto, contentType);
// need templates
var templateId = dto.DocumentVersionDto.TemplateId;
if (templateId.HasValue && templateId.Value > 0)
templateIds.Add(templateId.Value);
if (dto.Published)
if (!slim)
{
templateId = dto.PublishedVersionDto.TemplateId;
// need templates
var templateId = dto.DocumentVersionDto.TemplateId;
if (templateId.HasValue && templateId.Value > 0)
templateIds.Add(templateId.Value);
if (dto.Published)
{
templateId = dto.PublishedVersionDto.TemplateId;
if (templateId.HasValue && templateId.Value > 0)
templateIds.Add(templateId.Value);
}
}
// need properties
// need temps, for properties, templates and variations
var versionId = dto.DocumentVersionDto.Id;
var publishedVersionId = dto.Published ? dto.PublishedVersionDto.Id : 0;
var temp = new TempContent<Content>(dto.NodeId, versionId, publishedVersionId, contentType, c)
@@ -965,25 +979,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
temps.Add(temp);
}
// load all required templates in 1 query, and index
var templates = _templateRepository.GetMany(templateIds.ToArray())
.ToDictionary(x => x.Id, x => x);
// load all properties for all documents from database in 1 query - indexed by version id
var properties = GetPropertyCollections(temps);
// assign templates and properties
foreach (var temp in temps)
if (!slim)
{
// complete the item
if (temp.Template1Id.HasValue && templates.TryGetValue(temp.Template1Id.Value, out var template))
temp.Content.Template = template;
if (temp.Template2Id.HasValue && templates.TryGetValue(temp.Template2Id.Value, out template))
temp.Content.PublishTemplate = template;
temp.Content.Properties = properties[temp.VersionId];
// load all required templates in 1 query, and index
var templates = _templateRepository.GetMany(templateIds.ToArray())
.ToDictionary(x => x.Id, x => x);
// reset dirty initial properties (U4-1946)
temp.Content.ResetDirtyProperties(false);
// load all properties for all documents from database in 1 query - indexed by version id
var properties = GetPropertyCollections(temps);
// assign templates and properties
foreach (var temp in temps)
{
// complete the item
if (temp.Template1Id.HasValue && templates.TryGetValue(temp.Template1Id.Value, out var template))
temp.Content.Template = template;
if (temp.Template2Id.HasValue && templates.TryGetValue(temp.Template2Id.Value, out template))
temp.Content.PublishTemplate = template;
temp.Content.Properties = properties[temp.VersionId];
// reset dirty initial properties (U4-1946)
temp.Content.ResetDirtyProperties(false);
}
}
// set variations, if varying

View File

@@ -8,12 +8,12 @@ using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -100,7 +100,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
case QueryType.Many:
sql = sql.Select<ContentDto>(r =>
r.Select(x => x.NodeDto)
.Select(x => x.ContentVersionDto));
.Select(x => x.ContentVersionDto))
// ContentRepositoryBase expects a variantName field to order by name
// for now, just return the plain invariant node name
// fixme media should support variants !!
.AndSelect<NodeDto>(x => Alias(x.Text, "variantName"));
break;
}

View File

@@ -6,12 +6,12 @@ using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics;
namespace Umbraco.Core.Persistence.Repositories.Implement
{
@@ -114,9 +114,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
case QueryType.Single:
case QueryType.Many:
sql = sql.Select<MemberDto>(r =>
r.Select(x => x.ContentVersionDto)
.Select(x => x.ContentDto, r1 =>
r1.Select(x => x.NodeDto)));
r.Select(x => x.ContentVersionDto)
.Select(x => x.ContentDto, r1 =>
r1.Select(x => x.NodeDto)))
// ContentRepositoryBase expects a variantName field to order by name
// so get it here, though for members it's just the plain node name
.AndSelect<NodeDto>(x => Alias(x.Text, "variantName"));
break;
}

View File

@@ -17,11 +17,11 @@ namespace Umbraco.Core.Persistence
/// <param name="expression">An expression to visit.</param>
/// <param name="alias">An optional table alias.</param>
/// <returns>A SQL statement, and arguments, corresponding to the expression.</returns>
public static (string Sql, object[] Args) Visit<TDto>(this ISqlContext sqlContext, Expression<Func<TDto, object>> expression, string alias = null)
public static (string Sql, object[] Args) VisitDto<TDto>(this ISqlContext sqlContext, Expression<Func<TDto, object>> expression, string alias = null)
{
var expresionist = new PocoToSqlExpressionVisitor<TDto>(sqlContext, alias);
var visited = expresionist.Visit(expression);
return (visited, expresionist.GetSqlParameters());
var visitor = new PocoToSqlExpressionVisitor<TDto>(sqlContext, alias);
var visited = visitor.Visit(expression);
return (visited, visitor.GetSqlParameters());
}
/// <summary>
@@ -33,11 +33,11 @@ namespace Umbraco.Core.Persistence
/// <param name="expression">An expression to visit.</param>
/// <param name="alias">An optional table alias.</param>
/// <returns>A SQL statement, and arguments, corresponding to the expression.</returns>
public static (string Sql, object[] Args) Visit<TDto, TOut>(this ISqlContext sqlContext, Expression<Func<TDto, TOut>> expression, string alias = null)
public static (string Sql, object[] Args) VisitDto<TDto, TOut>(this ISqlContext sqlContext, Expression<Func<TDto, TOut>> expression, string alias = null)
{
var expresionist = new PocoToSqlExpressionVisitor<TDto>(sqlContext, alias);
var visited = expresionist.Visit(expression);
return (visited, expresionist.GetSqlParameters());
var visitor = new PocoToSqlExpressionVisitor<TDto>(sqlContext, alias);
var visited = visitor.Visit(expression);
return (visited, visitor.GetSqlParameters());
}
/// <summary>
@@ -50,11 +50,11 @@ namespace Umbraco.Core.Persistence
/// <param name="alias1">An optional table alias for the first DTO.</param>
/// <param name="alias2">An optional table alias for the second DTO.</param>
/// <returns>A SQL statement, and arguments, corresponding to the expression.</returns>
public static (string Sql, object[] Args) Visit<TDto1, TDto2>(this ISqlContext sqlContext, Expression<Func<TDto1, TDto2, object>> expression, string alias1 = null, string alias2 = null)
public static (string Sql, object[] Args) VisitDto<TDto1, TDto2>(this ISqlContext sqlContext, Expression<Func<TDto1, TDto2, object>> expression, string alias1 = null, string alias2 = null)
{
var expresionist = new PocoToSqlExpressionVisitor<TDto1, TDto2>(sqlContext, alias1, alias2);
var visited = expresionist.Visit(expression);
return (visited, expresionist.GetSqlParameters());
var visitor = new PocoToSqlExpressionVisitor<TDto1, TDto2>(sqlContext, alias1, alias2);
var visited = visitor.Visit(expression);
return (visited, visitor.GetSqlParameters());
}
/// <summary>
@@ -68,11 +68,42 @@ namespace Umbraco.Core.Persistence
/// <param name="alias1">An optional table alias for the first DTO.</param>
/// <param name="alias2">An optional table alias for the second DTO.</param>
/// <returns>A SQL statement, and arguments, corresponding to the expression.</returns>
public static (string Sql, object[] Args) Visit<TDto1, TDto2, TOut>(this ISqlContext sqlContext, Expression<Func<TDto1, TDto2, TOut>> expression, string alias1 = null, string alias2 = null)
public static (string Sql, object[] Args) VisitDto<TDto1, TDto2, TOut>(this ISqlContext sqlContext, Expression<Func<TDto1, TDto2, TOut>> expression, string alias1 = null, string alias2 = null)
{
var expresionist = new PocoToSqlExpressionVisitor<TDto1, TDto2>(sqlContext, alias1, alias2);
var visited = expresionist.Visit(expression);
return (visited, expresionist.GetSqlParameters());
var visitor = new PocoToSqlExpressionVisitor<TDto1, TDto2>(sqlContext, alias1, alias2);
var visited = visitor.Visit(expression);
return (visited, visitor.GetSqlParameters());
}
/// <summary>
/// Visit a model expression.
/// </summary>
/// <typeparam name="TModel">The type of the model.</typeparam>
/// <param name="sqlContext">An <see cref="ISqlContext"/>.</param>
/// <param name="expression">An expression to visit.</param>
/// <returns>A SQL statement, and arguments, corresponding to the expression.</returns>
public static (string Sql, object[] Args) VisitModel<TModel>(this ISqlContext sqlContext, Expression<Func<TModel, object>> expression)
{
var visitor = new ModelToSqlExpressionVisitor<TModel>(sqlContext.SqlSyntax, sqlContext.Mappers);
var visited = visitor.Visit(expression);
return (visited, visitor.GetSqlParameters());
}
/// <summary>
/// Visit a model expression representing a field.
/// </summary>
/// <typeparam name="TModel">The type of the model.</typeparam>
/// <param name="sqlContext">An <see cref="ISqlContext"/>.</param>
/// <param name="field">An expression to visit, representing a field.</param>
/// <returns>The name of the field.</returns>
public static string VisitModelField<TModel>(this ISqlContext sqlContext, Expression<Func<TModel, object>> field)
{
var (sql, _) = sqlContext.VisitModel(field);
// going to return "<field> = @0"
// take the first part only
var pos = sql.IndexOf(' ');
return sql.Substring(0, pos);
}
}
}
}

View File

@@ -307,9 +307,9 @@ namespace Umbraco.Core.Runtime
throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database.");
}
// if we already know we want to upgrade, no need to look for migrations...
if (_state.Level == RuntimeLevel.Upgrade)
return;
// if we already know we want to upgrade,
// still run EnsureUmbracoUpgradeState to get the states
// (v7 will just get a null state, that's ok)
// else
// look for a matching migration entry - bypassing services entirely - they are not 'up' yet

View File

@@ -134,6 +134,12 @@ namespace Umbraco.Core.Services
/// <remarks>Versions are ordered with current first, then most recent first.</remarks>
IEnumerable<IContent> GetVersions(int id);
/// <summary>
/// Gets all versions of a document.
/// </summary>
/// <remarks>Versions are ordered with current first, then most recent first.</remarks>
IEnumerable<IContent> GetVersionsSlim(int id, int skip, int take);
/// <summary>
/// Gets top versions of a document.
/// </summary>
@@ -461,5 +467,21 @@ namespace Umbraco.Core.Services
IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = 0);
#endregion
#region Rollback
/// <summary>
/// Rolls back the content to a specific version.
/// </summary>
/// <param name="id">The id of the content node.</param>
/// <param name="versionId">The version id to roll back to.</param>
/// <param name="culture">An optional culture to roll back.</param>
/// <param name="userId">The identifier of the user who is performing the roll back.</param>
/// <remarks>
/// <para>When no culture is specified, all cultures are rolled back.</para>
/// </remarks>
OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0);
#endregion
}
}

View File

@@ -474,6 +474,19 @@ namespace Umbraco.Core.Services.Implement
}
}
/// <summary>
/// Gets a collection of an <see cref="IContent"/> objects versions by Id
/// </summary>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable<IContent> GetVersionsSlim(int id, int skip, int take)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(Constants.Locks.ContentTree);
return _documentRepository.GetAllVersionsSlim(id, skip, take);
}
}
/// <summary>
/// Gets a list of all version Ids for the given content item ordered so latest is first
/// </summary>
@@ -2538,5 +2551,68 @@ namespace Umbraco.Core.Services.Implement
}
#endregion
#region Rollback
public OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0)
{
var evtMsgs = EventMessagesFactory.Get();
//Get the current copy of the node
var content = GetById(id);
//Get the version
var version = GetVersion(versionId);
//Good ole null checks
if (content == null || version == null)
{
return new OperationResult(OperationResultType.FailedCannot, evtMsgs);
}
//Store the result of doing the save of content for the rollback
OperationResult rollbackSaveResult;
using (var scope = ScopeProvider.CreateScope())
{
var rollbackEventArgs = new RollbackEventArgs<IContent>(content);
//Emit RollingBack event aka before
if (scope.Events.DispatchCancelable(RollingBack, this, rollbackEventArgs))
{
scope.Complete();
return OperationResult.Cancel(evtMsgs);
}
//Copy the changes from the version
content.CopyFrom(version, culture);
//Save the content for the rollback
rollbackSaveResult = Save(content, userId);
//Depending on the save result - is what we log & audit along with what we return
if (rollbackSaveResult.Success == false)
{
//Log the error/warning
Logger.Error<ContentService>("User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId, id, versionId);
}
else
{
//Emit RolledBack event aka after
rollbackEventArgs.CanCancel = false;
scope.Events.Dispatch(RolledBack, this, rollbackEventArgs);
//Logging & Audit message
Logger.Info<ContentService>("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", userId, id, versionId);
Audit(AuditType.RollBack, $"Content '{content.Name}' was rolled back to version '{versionId}'", userId, id);
}
scope.Complete();
}
return rollbackSaveResult;
}
#endregion
}
}