Merge branch 'temp8' into temp8-dirty-tracking-on-variants
This commit is contained in:
@@ -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.
|
||||
// ]
|
||||
// },
|
||||
// ...
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user