Fix issues with added PanicException

This commit is contained in:
Ronald Barendse
2019-11-15 09:15:12 +01:00
859 changed files with 31488 additions and 22622 deletions

View File

@@ -470,7 +470,7 @@ namespace Umbraco.Core.Persistence
break;
case SqlDbType.SmallInt:
dataType = typeof(Int16);
dataType = typeof(short);
dataTypeName = "smallint";
break;
@@ -688,34 +688,34 @@ namespace Umbraco.Core.Persistence
DataColumnCollection columns = _schemaTable.Columns;
columns.Add(SchemaTableColumn.ColumnName, typeof(System.String));
columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(System.Int32));
columns.Add(SchemaTableColumn.ColumnSize, typeof(System.Int32));
columns.Add(SchemaTableColumn.NumericPrecision, typeof(System.Int16));
columns.Add(SchemaTableColumn.NumericScale, typeof(System.Int16));
columns.Add(SchemaTableColumn.IsUnique, typeof(System.Boolean));
columns.Add(SchemaTableColumn.IsKey, typeof(System.Boolean));
columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(System.String));
columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(System.String));
columns.Add(SchemaTableColumn.BaseColumnName, typeof(System.String));
columns.Add(SchemaTableColumn.BaseSchemaName, typeof(System.String));
columns.Add(SchemaTableColumn.BaseTableName, typeof(System.String));
columns.Add(SchemaTableColumn.DataType, typeof(System.Type));
columns.Add(SchemaTableColumn.AllowDBNull, typeof(System.Boolean));
columns.Add(SchemaTableColumn.ProviderType, typeof(System.Int32));
columns.Add(SchemaTableColumn.IsAliased, typeof(System.Boolean));
columns.Add(SchemaTableColumn.IsExpression, typeof(System.Boolean));
columns.Add(BulkDataReader.IsIdentitySchemaColumn, typeof(System.Boolean));
columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(System.Boolean));
columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(System.Boolean));
columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(System.Boolean));
columns.Add(SchemaTableColumn.IsLong, typeof(System.Boolean));
columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(System.Boolean));
columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(System.Type));
columns.Add(BulkDataReader.DataTypeNameSchemaColumn, typeof(System.String));
columns.Add(BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn, typeof(System.String));
columns.Add(BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn, typeof(System.String));
columns.Add(BulkDataReader.XmlSchemaCollectionNameSchemaColumn, typeof(System.String));
columns.Add(SchemaTableColumn.ColumnName, typeof(string));
columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int));
columns.Add(SchemaTableColumn.ColumnSize, typeof(int));
columns.Add(SchemaTableColumn.NumericPrecision, typeof(short));
columns.Add(SchemaTableColumn.NumericScale, typeof(short));
columns.Add(SchemaTableColumn.IsUnique, typeof(bool));
columns.Add(SchemaTableColumn.IsKey, typeof(bool));
columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string));
columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(string));
columns.Add(SchemaTableColumn.BaseColumnName, typeof(string));
columns.Add(SchemaTableColumn.BaseSchemaName, typeof(string));
columns.Add(SchemaTableColumn.BaseTableName, typeof(string));
columns.Add(SchemaTableColumn.DataType, typeof(Type));
columns.Add(SchemaTableColumn.AllowDBNull, typeof(bool));
columns.Add(SchemaTableColumn.ProviderType, typeof(int));
columns.Add(SchemaTableColumn.IsAliased, typeof(bool));
columns.Add(SchemaTableColumn.IsExpression, typeof(bool));
columns.Add(BulkDataReader.IsIdentitySchemaColumn, typeof(bool));
columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool));
columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(bool));
columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(bool));
columns.Add(SchemaTableColumn.IsLong, typeof(bool));
columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(bool));
columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(Type));
columns.Add(BulkDataReader.DataTypeNameSchemaColumn, typeof(string));
columns.Add(BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn, typeof(string));
columns.Add(BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn, typeof(string));
columns.Add(BulkDataReader.XmlSchemaCollectionNameSchemaColumn, typeof(string));
}
#endregion
@@ -1090,7 +1090,7 @@ namespace Umbraco.Core.Persistence
/// <seealso cref="IDataRecord.GetDecimal(Int32)"/>
public decimal GetDecimal(int i)
{
return (Decimal)GetValue(i);
return (decimal)GetValue(i);
}
/// <summary>

View File

@@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Runtime.CompilerServices;
namespace Umbraco.Core.Persistence
{
internal static class DatabaseNodeLockExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidateDatabase(IUmbracoDatabase database)
{
if (database == null)
throw new ArgumentNullException("database");
if (database.GetCurrentTransactionIsolationLevel() < IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
}
// updating a record within a repeatable-read transaction gets an exclusive lock on
// that record which will be kept until the transaction is ended, effectively locking
// out all other accesses to that record - thus obtaining an exclusive lock over the
// protected resources.
public static void AcquireLockNodeWriteLock(this IUmbracoDatabase database, int nodeId)
{
ValidateDatabase(database);
database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id",
new { @id = nodeId });
}
// reading a record within a repeatable-read transaction gets a shared lock on
// that record which will be kept until the transaction is ended, effectively preventing
// other write accesses to that record - thus obtaining a shared lock over the protected
// resources.
public static void AcquireLockNodeReadLock(this IUmbracoDatabase database, int nodeId)
{
ValidateDatabase(database);
database.ExecuteScalar<int>("SELECT value FROM umbracoLock WHERE id=@id",
new { @id = nodeId });
}
}
}

View File

@@ -3,8 +3,6 @@ using System.Runtime.Serialization;
namespace Umbraco.Core.Persistence
{
// TODO: Would be good to use this exception type anytime we cannot find an entity
/// <summary>
/// An exception used to indicate that an Umbraco entity could not be found.
/// </summary>

View File

@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence.Factories
public static IEnumerable<Property> BuildEntities(PropertyType[] propertyTypes, IReadOnlyCollection<PropertyDataDto> dtos, int publishedVersionId, ILanguageRepository languageRepository)
{
var properties = new List<Property>();
var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable<PropertyDataDto>) x);
var xdtos = dtos.GroupBy(x => x.PropertyTypeId).ToDictionary(x => x.Key, x => (IEnumerable<PropertyDataDto>)x);
foreach (var propertyType in propertyTypes)
{
@@ -104,10 +104,14 @@ namespace Umbraco.Core.Persistence.Factories
/// <param name="properties">The properties to map</param>
/// <param name="languageRepository"></param>
/// <param name="edited">out parameter indicating that one or more properties have been edited</param>
/// <param name="editedCultures">out parameter containing a collection of edited cultures when the contentVariation varies by culture</param>
/// <param name="editedCultures">
/// Out parameter containing a collection of edited cultures when the contentVariation varies by culture.
/// The value of this will be used to populate the edited cultures in the umbracoDocumentCultureVariation table.
/// </param>
/// <returns></returns>
public static IEnumerable<PropertyDataDto> BuildDtos(ContentVariation contentVariation, int currentVersionId, int publishedVersionId, IEnumerable<Property> properties,
ILanguageRepository languageRepository, out bool edited, out HashSet<string> editedCultures)
ILanguageRepository languageRepository, out bool edited,
out HashSet<string> editedCultures)
{
var propertyDataDtos = new List<PropertyDataDto>();
edited = false;
@@ -130,6 +134,9 @@ namespace Umbraco.Core.Persistence.Factories
// publishing = deal with edit and published values
foreach (var propertyValue in property.Values)
{
var isInvariantValue = propertyValue.Culture == null;
var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null;
// deal with published value
if (propertyValue.PublishedValue != null && publishedVersionId > 0)
propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue));
@@ -138,26 +145,36 @@ namespace Umbraco.Core.Persistence.Factories
if (propertyValue.EditedValue != null)
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
// property.Values will contain ALL of it's values, both variant and invariant which will be populated if the
// administrator has previously changed the property type to be variant vs invariant.
// We need to check for this scenario here because otherwise the editedCultures and edited flags
// will end up incorrectly set in the umbracoDocumentCultureVariation table so here we need to
// only process edited cultures based on the current value type and how the property varies.
// The above logic will still persist the currently saved property value for each culture in case the admin
// decides to swap the property's variance again, in which case the edited flag will be recalculated.
if (property.PropertyType.VariesByCulture() && isInvariantValue || !property.PropertyType.VariesByCulture() && isCultureValue)
continue;
// use explicit equals here, else object comparison fails at comparing eg strings
var sameValues = propertyValue.PublishedValue == null ? propertyValue.EditedValue == null : propertyValue.PublishedValue.Equals(propertyValue.EditedValue);
edited |= !sameValues;
if (entityVariesByCulture // cultures can be edited, ie CultureNeutral is supported
&& propertyValue.Culture != null && propertyValue.Segment == null // and value is CultureNeutral
&& !sameValues) // and edited and published are different
if (entityVariesByCulture && !sameValues)
{
editedCultures.Add(propertyValue.Culture); // report culture as edited
}
if (isCultureValue)
{
editedCultures.Add(propertyValue.Culture); // report culture as edited
}
else if (isInvariantValue)
{
// flag culture as edited if it contains an edited invariant property
if (defaultCulture == null)
defaultCulture = languageRepository.GetDefaultIsoCode();
// flag culture as edited if it contains an edited invariant property
if (propertyValue.Culture == null //invariant property
&& !sameValues // and edited and published are different
&& entityVariesByCulture) //only when the entity is variant
{
if (defaultCulture == null)
defaultCulture = languageRepository.GetDefaultIsoCode();
editedCultures.Add(defaultCulture);
editedCultures.Add(defaultCulture);
}
}
}
}
@@ -167,7 +184,7 @@ namespace Umbraco.Core.Persistence.Factories
{
// not publishing = only deal with edit values
if (propertyValue.EditedValue != null)
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
}
edited = true;
}

View File

@@ -7,5 +7,12 @@ namespace Umbraco.Core.Persistence.Repositories
public interface IDataTypeRepository : IReadWriteQueryRepository<int, IDataType>
{
IEnumerable<MoveEventInfo<IDataType>> Move(IDataType toMove, EntityContainer container);
/// <summary>
/// Returns a dictionary of content type <see cref="Udi"/>s and the property type aliases that use a <see cref="IDataType"/>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
IReadOnlyDictionary<Udi, IEnumerable<string>> FindUsages(int id);
}
}

View File

@@ -174,7 +174,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
totalRecords = page.TotalItems;
var items = page.Items.Select(
dto => new AuditItem(dto.Id, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList();
dto => new AuditItem(dto.NodeId, Enum<AuditType>.ParseOrNull(dto.Header) ?? AuditType.Custom, dto.UserId ?? Constants.Security.UnknownUserId, dto.EntityType, dto.Comment, dto.Parameters)).ToList();
// map the DateStamp
for (var i = 0; i < items.Count; i++)

View File

@@ -512,31 +512,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
foreach (var a in allPropertyDataDtos)
a.PropertyTypeDto = indexedPropertyTypeDtos[a.PropertyTypeId];
// prefetch configuration for tag properties
var tagEditors = new Dictionary<string, TagConfiguration>();
foreach (var propertyTypeDto in indexedPropertyTypeDtos.Values)
{
var editorAlias = propertyTypeDto.DataTypeDto.EditorAlias;
var editorAttribute = PropertyEditors[editorAlias].GetTagAttribute();
if (editorAttribute == null) continue;
var tagConfigurationSource = propertyTypeDto.DataTypeDto.Configuration;
var tagConfiguration = string.IsNullOrWhiteSpace(tagConfigurationSource)
? new TagConfiguration()
: JsonConvert.DeserializeObject<TagConfiguration>(tagConfigurationSource);
if (tagConfiguration.Delimiter == default) tagConfiguration.Delimiter = editorAttribute.Delimiter;
tagEditors[editorAlias] = tagConfiguration;
}
// now we have
// - the definitions
// - all property data dtos
// - tag editors
// - tag editors (Actually ... no we don't since i removed that code, but we don't need them anyways it seems)
// and we need to build the proper property collections
return GetPropertyCollections(temps, allPropertyDataDtos, tagEditors);
return GetPropertyCollections(temps, allPropertyDataDtos);
}
private IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos, Dictionary<string, TagConfiguration> tagConfigurations)
private IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<TempContent<T>> temps, IEnumerable<PropertyDataDto> allPropertyDataDtos)
where T : class, IContentBase
{
var result = new Dictionary<int, PropertyCollection>();

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Factories;
@@ -90,7 +91,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
contentType = ContentTypeFactory.BuildContentTypeEntity(contentTypeDto);
else if (contentTypeDto.NodeDto.NodeObjectType == Constants.ObjectTypes.MemberType)
contentType = ContentTypeFactory.BuildMemberTypeEntity(contentTypeDto);
else throw new Exception("panic");
else throw new PanicException($"The node object type {contentTypeDto.NodeDto.NodeObjectType} is not supported");
contentTypes.Add(contentType.Id, contentType);
// map allowed content types

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </summary>
internal class ContentTypeRepository : ContentTypeRepositoryBase<IContentType>, IContentTypeRepository
{
public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
: base(scopeAccessor, cache, logger, commonRepository)
public ContentTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => ContentType.SupportsPublishingConst;
@@ -56,7 +57,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
if (ids.Any()) throw new Exception("panic");
if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType<IContentType>();
}

View File

@@ -1,4 +1,5 @@
using System;

using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
@@ -15,6 +16,7 @@ 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
{
@@ -26,14 +28,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
internal abstract class ContentTypeRepositoryBase<TEntity> : NPocoRepositoryBase<int, TEntity>, IReadRepository<Guid, TEntity>
where TEntity : class, IContentTypeComposition
{
protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
protected ContentTypeRepositoryBase(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger)
{
CommonRepository = commonRepository;
LanguageRepository = languageRepository;
}
protected IContentTypeCommonRepository CommonRepository { get; }
protected ILanguageRepository LanguageRepository { get; }
protected abstract bool SupportsPublishing { get; }
public IEnumerable<MoveEventInfo<TEntity>> Move(TEntity moving, EntityContainer container)
@@ -98,6 +101,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected void PersistNewBaseContentType(IContentTypeComposition entity)
{
ValidateVariations(entity);
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
//Cannot add a duplicate content type
@@ -163,11 +168,11 @@ AND umbracoNode.nodeObjectType = @objectType",
foreach (var allowedContentType in entity.AllowedContentTypes)
{
Database.Insert(new ContentTypeAllowedContentTypeDto
{
Id = entity.Id,
AllowedId = allowedContentType.Id.Value,
SortOrder = allowedContentType.SortOrder
});
{
Id = entity.Id,
AllowedId = allowedContentType.Id.Value,
SortOrder = allowedContentType.SortOrder
});
}
@@ -214,6 +219,9 @@ AND umbracoNode.nodeObjectType = @objectType",
protected void PersistUpdatedBaseContentType(IContentTypeComposition entity)
{
CorrectPropertyTypeVariations(entity);
ValidateVariations(entity);
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
// ensure the alias is not used already
@@ -370,7 +378,7 @@ AND umbracoNode.id <> @id",
foreach (var propertyGroup in entity.PropertyGroups)
{
// insert or update group
var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup,entity.Id);
var groupDto = PropertyGroupFactory.BuildGroupDto(propertyGroup, entity.Id);
var groupId = propertyGroup.HasIdentity
? Database.Update(groupDto)
: Convert.ToInt32(Database.Insert(groupDto));
@@ -388,7 +396,7 @@ AND umbracoNode.id <> @id",
//check if the content type variation has been changed
var contentTypeVariationDirty = entity.IsPropertyDirty("Variations");
var oldContentTypeVariation = (ContentVariation) dtoPk.Variations;
var oldContentTypeVariation = (ContentVariation)dtoPk.Variations;
var newContentTypeVariation = entity.Variations;
var contentTypeVariationChanging = contentTypeVariationDirty && oldContentTypeVariation != newContentTypeVariation;
if (contentTypeVariationChanging)
@@ -404,26 +412,7 @@ AND umbracoNode.id <> @id",
// note: this only deals with *local* property types, we're dealing w/compositions later below
foreach (var propertyType in entity.PropertyTypes)
{
if (contentTypeVariationChanging)
{
// content type is changing
switch (newContentTypeVariation)
{
case ContentVariation.Nothing: // changing to Nothing
// all property types must change to Nothing
propertyType.Variations = ContentVariation.Nothing;
break;
case ContentVariation.Culture: // changing to Culture
// all property types can remain Nothing
break;
case ContentVariation.CultureAndSegment:
case ContentVariation.Segment:
default:
throw new NotSupportedException(); // TODO: Support this
}
}
// then, track each property individually
// track each property individually
if (propertyType.IsPropertyDirty("Variations"))
{
// allocate the list only when needed
@@ -449,23 +438,19 @@ AND umbracoNode.id <> @id",
// via composition, with their original variations (ie not filtered by this
// content type variations - we need this true value to make decisions.
foreach (var propertyType in ((ContentTypeCompositionBase) entity).RawComposedPropertyTypes)
propertyTypeVariationChanges = propertyTypeVariationChanges ?? new Dictionary<int, (ContentVariation, ContentVariation)>();
foreach (var composedPropertyType in ((ContentTypeCompositionBase)entity).RawComposedPropertyTypes)
{
if (propertyType.VariesBySegment() || newContentTypeVariation.VariesBySegment())
throw new NotSupportedException(); // TODO: support this
if (composedPropertyType.Variations == ContentVariation.Nothing) continue;
if (propertyType.Variations == ContentVariation.Culture)
{
if (propertyTypeVariationChanges == null)
propertyTypeVariationChanges = new Dictionary<int, (ContentVariation, ContentVariation)>();
// Determine target variation of the composed property type.
// The composed property is only considered culture variant when the base content type is also culture variant.
// The composed property is only considered segment variant when the base content type is also segment variant.
// Example: Culture variant content type with a Culture+Segment variant property type will become ContentVariation.Culture
var target = newContentTypeVariation & composedPropertyType.Variations;
// if content type moves to Culture, property type becomes Culture here again
// if content type moves to Nothing, property type becomes Nothing here
if (newContentTypeVariation == ContentVariation.Culture)
propertyTypeVariationChanges[propertyType.Id] = (ContentVariation.Nothing, ContentVariation.Culture);
else if (newContentTypeVariation == ContentVariation.Nothing)
propertyTypeVariationChanges[propertyType.Id] = (ContentVariation.Culture, ContentVariation.Nothing);
}
propertyTypeVariationChanges[composedPropertyType.Id] = (composedPropertyType.Variations, target);
}
}
@@ -506,7 +491,7 @@ AND umbracoNode.id <> @id",
var impacted = GetImpactedContentTypes(entity, all);
// if some property types have actually changed, move their variant data
if (propertyTypeVariationChanges != null)
if (propertyTypeVariationChanges?.Count > 0)
MovePropertyTypeVariantData(propertyTypeVariationChanges, impacted);
// deal with orphan properties: those that were in a deleted tab,
@@ -518,6 +503,42 @@ AND umbracoNode.id <> @id",
CommonRepository.ClearCache(); // always
}
/// <summary>
/// Corrects the property type variations for the given entity
/// to make sure the property type variation is compatible with the
/// variation set on the entity itself.
/// </summary>
/// <param name="entity">Entity to correct properties for</param>
private void CorrectPropertyTypeVariations(IContentTypeComposition entity)
{
// Update property variations based on the content type variation
foreach (var propertyType in entity.PropertyTypes)
{
// Determine variation for the property type.
// The property is only considered culture variant when the base content type is also culture variant.
// The property is only considered segment variant when the base content type is also segment variant.
// Example: Culture variant content type with a Culture+Segment variant property type will become ContentVariation.Culture
propertyType.Variations = entity.Variations & propertyType.Variations;
}
}
/// <summary>
/// Ensures that no property types are flagged for a variance that is not supported by the content type itself
/// </summary>
/// <param name="entity">The entity for which the property types will be validated</param>
private void ValidateVariations(IContentTypeComposition entity)
{
foreach (var prop in entity.PropertyTypes)
{
// The variation of a property is only allowed if all its variation flags
// are also set on the entity itself. It cannot set anything that is not also set by the content type.
// For example, when entity.Variations is set to Culture a property cannot be set to Segment.
var isValid = entity.Variations.HasFlag(prop.Variations);
if (!isValid)
throw new InvalidOperationException($"The property {prop.Alias} cannot have variations of {prop.Variations} with the content type variations of {entity.Variations}");
}
}
private IEnumerable<IContentTypeComposition> GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable<IContentTypeComposition> all)
{
var impact = new List<IContentTypeComposition>();
@@ -525,12 +546,12 @@ AND umbracoNode.id <> @id",
var tree = new Dictionary<int, List<IContentTypeComposition>>();
foreach (var x in all)
foreach (var y in x.ContentTypeComposition)
{
if (!tree.TryGetValue(y.Id, out var list))
list = tree[y.Id] = new List<IContentTypeComposition>();
list.Add(x);
}
foreach (var y in x.ContentTypeComposition)
{
if (!tree.TryGetValue(y.Id, out var list))
list = tree[y.Id] = new List<IContentTypeComposition>();
list.Add(x);
}
var nset = new List<IContentTypeComposition>();
do
@@ -572,7 +593,7 @@ AND umbracoNode.id <> @id",
// new property type, ignore
if (!oldVariations.TryGetValue(propertyType.Id, out var oldVariationB))
continue;
var oldVariation = (ContentVariation) oldVariationB; // NPoco cannot fetch directly
var oldVariation = (ContentVariation)oldVariationB; // NPoco cannot fetch directly
// only those property types that *actually* changed
var newVariation = propertyType.Variations;
@@ -636,25 +657,27 @@ AND umbracoNode.id <> @id",
var impactedL = impacted.Select(x => x.Id).ToList();
//Group by the "To" variation so we can bulk update in the correct batches
foreach(var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
foreach (var grouping in propertyTypeChanges.GroupBy(x => x.Value))
{
var propertyTypeIds = grouping.Select(x => x.Key).ToList();
var toVariation = grouping.Key;
var (FromVariation, ToVariation) = grouping.Key;
switch (toVariation)
var fromCultureEnabled = FromVariation.HasFlag(ContentVariation.Culture);
var toCultureEnabled = ToVariation.HasFlag(ContentVariation.Culture);
if (!fromCultureEnabled && toCultureEnabled)
{
case ContentVariation.Culture:
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
break;
case ContentVariation.Nothing:
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
break;
case ContentVariation.CultureAndSegment:
case ContentVariation.Segment:
default:
throw new NotSupportedException(); // TODO: Support this
// Culture has been enabled
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
}
else if (fromCultureEnabled && !toCultureEnabled)
{
// Culture has been disabled
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
}
}
}
@@ -666,78 +689,72 @@ AND umbracoNode.id <> @id",
{
var defaultLanguageId = GetDefaultLanguageId();
switch (toVariation)
var cultureIsNotEnabled = !fromVariation.HasFlag(ContentVariation.Culture);
var cultureWillBeEnabled = toVariation.HasFlag(ContentVariation.Culture);
if (cultureIsNotEnabled && cultureWillBeEnabled)
{
case ContentVariation.Culture:
//move the names
//first clear out any existing names that might already exists under the default lang
//there's 2x tables to update
//move the names
//first clear out any existing names that might already exists under the default lang
//there's 2x tables to update
//clear out the versionCultureVariation table
var sqlSelect = Sql().Select<ContentVersionCultureVariationDto>(x => x.Id)
.From<ContentVersionCultureVariationDto>()
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, ContentVersionCultureVariationDto>(x => x.Id, x => x.VersionId)
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
.Where<ContentVersionCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
var sqlDelete = Sql()
.Delete<ContentVersionCultureVariationDto>()
.WhereIn<ContentVersionCultureVariationDto>(x => x.Id, sqlSelect);
//clear out the versionCultureVariation table
var sqlSelect = Sql().Select<ContentVersionCultureVariationDto>(x => x.Id)
.From<ContentVersionCultureVariationDto>()
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, ContentVersionCultureVariationDto>(x => x.Id, x => x.VersionId)
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
.Where<ContentVersionCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
var sqlDelete = Sql()
.Delete<ContentVersionCultureVariationDto>()
.WhereIn<ContentVersionCultureVariationDto>(x => x.Id, sqlSelect);
Database.Execute(sqlDelete);
Database.Execute(sqlDelete);
//clear out the documentCultureVariation table
sqlSelect = Sql().Select<DocumentCultureVariationDto>(x => x.Id)
.From<DocumentCultureVariationDto>()
.InnerJoin<ContentDto>().On<ContentDto, DocumentCultureVariationDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
.Where<DocumentCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
sqlDelete = Sql()
.Delete<DocumentCultureVariationDto>()
.WhereIn<DocumentCultureVariationDto>(x => x.Id, sqlSelect);
//clear out the documentCultureVariation table
sqlSelect = Sql().Select<DocumentCultureVariationDto>(x => x.Id)
.From<DocumentCultureVariationDto>()
.InnerJoin<ContentDto>().On<ContentDto, DocumentCultureVariationDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
.Where<DocumentCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
sqlDelete = Sql()
.Delete<DocumentCultureVariationDto>()
.WhereIn<DocumentCultureVariationDto>(x => x.Id, sqlSelect);
Database.Execute(sqlDelete);
Database.Execute(sqlDelete);
//now we need to insert names into these 2 tables based on the invariant data
//now we need to insert names into these 2 tables based on the invariant data
//insert rows into the versionCultureVariationDto table based on the data from contentVersionDto for the default lang
var cols = Sql().Columns<ContentVersionCultureVariationDto>(x => x.VersionId, x => x.Name, x => x.UpdateUserId, x => x.UpdateDate, x => x.LanguageId);
sqlSelect = Sql().Select<ContentVersionDto>(x => x.Id, x => x.Text, x => x.UserId, x => x.VersionDate)
.Append($", {defaultLanguageId}") //default language ID
.From<ContentVersionDto>()
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
var sqlInsert = Sql($"INSERT INTO {ContentVersionCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
//insert rows into the versionCultureVariationDto table based on the data from contentVersionDto for the default lang
var cols = Sql().Columns<ContentVersionCultureVariationDto>(x => x.VersionId, x => x.Name, x => x.UpdateUserId, x => x.UpdateDate, x => x.LanguageId);
sqlSelect = Sql().Select<ContentVersionDto>(x => x.Id, x => x.Text, x => x.UserId, x => x.VersionDate)
.Append($", {defaultLanguageId}") //default language ID
.From<ContentVersionDto>()
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
var sqlInsert = Sql($"INSERT INTO {ContentVersionCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
Database.Execute(sqlInsert);
Database.Execute(sqlInsert);
//insert rows into the documentCultureVariation table
cols = Sql().Columns<DocumentCultureVariationDto>(x => x.NodeId, x => x.Edited, x => x.Published, x => x.Name, x => x.Available, x => x.LanguageId);
sqlSelect = Sql().Select<DocumentDto>(x => x.NodeId, x => x.Edited, x => x.Published)
.AndSelect<NodeDto>(x => x.Text)
.Append($", 1, {defaultLanguageId}") //make Available + default language ID
.From<DocumentDto>()
.InnerJoin<NodeDto>().On<NodeDto, DocumentDto>(x => x.NodeId, x => x.NodeId)
.InnerJoin<ContentDto>().On<ContentDto, NodeDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
sqlInsert = Sql($"INSERT INTO {DocumentCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
//insert rows into the documentCultureVariation table
cols = Sql().Columns<DocumentCultureVariationDto>(x => x.NodeId, x => x.Edited, x => x.Published, x => x.Name, x => x.Available, x => x.LanguageId);
sqlSelect = Sql().Select<DocumentDto>(x => x.NodeId, x => x.Edited, x => x.Published)
.AndSelect<NodeDto>(x => x.Text)
.Append($", 1, {defaultLanguageId}") //make Available + default language ID
.From<DocumentDto>()
.InnerJoin<NodeDto>().On<NodeDto, DocumentDto>(x => x.NodeId, x => x.NodeId)
.InnerJoin<ContentDto>().On<ContentDto, NodeDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
sqlInsert = Sql($"INSERT INTO {DocumentCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
Database.Execute(sqlInsert);
}
else
{
//we don't need to move the names! this is because we always keep the invariant names with the name of the default language.
Database.Execute(sqlInsert);
break;
case ContentVariation.Nothing:
//we don't need to move the names! this is because we always keep the invariant names with the name of the default language.
//however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :(
// if we want these SQL statements back, look into GIT history
break;
case ContentVariation.CultureAndSegment:
case ContentVariation.Segment:
default:
throw new NotSupportedException(); // TODO: Support this
//however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :(
// if we want these SQL statements back, look into GIT history
}
}
@@ -963,6 +980,205 @@ AND umbracoNode.id <> @id",
Database.Execute(sqlDelete);
}
}
/// <summary>
/// Re-normalizes the edited value in the umbracoDocumentCultureVariation and umbracoDocument table when variations are changed
/// </summary>
/// <param name="propertyTypeIds"></param>
/// <param name="contentTypeIds"></param>
/// <remarks>
/// If this is not done, then in some cases the "edited" value for a particular culture for a document will remain true when it should be false
/// if the property was changed to invariant. In order to do this we need to recalculate this value based on the values stored for each
/// property, culture and current/published version.
/// </remarks>
private void RenormalizeDocumentEditedFlags(IReadOnlyCollection<int> propertyTypeIds, IReadOnlyCollection<int> contentTypeIds = null)
{
var defaultLang = LanguageRepository.GetDefaultId();
//This will build up a query to get the property values of both the current and the published version so that we can check
//based on the current variance of each item to see if it's 'edited' value should be true/false.
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
if (whereInArgsCount > 2000)
throw new NotSupportedException("Too many property/content types.");
var propertySql = Sql()
.Select<PropertyDataDto>()
.AndSelect<ContentVersionDto>(x => x.NodeId, x => x.Current)
.AndSelect<DocumentVersionDto>(x => x.Published)
.AndSelect<PropertyTypeDto>(x => x.Variations)
.From<PropertyDataDto>()
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, PropertyDataDto>((left, right) => left.Id == right.VersionId)
.InnerJoin<PropertyTypeDto>().On<PropertyTypeDto, PropertyDataDto>((left, right) => left.Id == right.PropertyTypeId);
if (contentTypeIds != null)
{
propertySql.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>((c, cversion) => c.NodeId == cversion.NodeId);
}
propertySql.LeftJoin<DocumentVersionDto>().On<DocumentVersionDto, ContentVersionDto>((docversion, cversion) => cversion.Id == docversion.Id)
.Where<DocumentVersionDto, ContentVersionDto>((docversion, cversion) => cversion.Current || docversion.Published)
.WhereIn<PropertyDataDto>(x => x.PropertyTypeId, propertyTypeIds);
if (contentTypeIds != null)
{
propertySql.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIds);
}
propertySql
.OrderBy<ContentVersionDto>(x => x.NodeId)
.OrderBy<PropertyDataDto>(x => x.PropertyTypeId, x => x.LanguageId, x => x.VersionId);
//keep track of this node/lang to mark or unmark a culture as edited
var editedLanguageVersions = new Dictionary<(int nodeId, int? langId), bool>();
//keep track of which node to mark or unmark as edited
var editedDocument = new Dictionary<int, bool>();
var nodeId = -1;
var propertyTypeId = -1;
PropertyValueVersionDto pubRow = null;
//This is a reader (Query), we are not fetching this all into memory so we cannot make any changes during this iteration, we are just collecting data.
//Published data will always come before Current data based on the version id sort.
//There will only be one published row (max) and one current row per property.
foreach (var row in Database.Query<PropertyValueVersionDto>(propertySql))
{
//make sure to reset on each node/property change
if (nodeId != row.NodeId || propertyTypeId != row.PropertyTypeId)
{
nodeId = row.NodeId;
propertyTypeId = row.PropertyTypeId;
pubRow = null;
}
if (row.Published)
pubRow = row;
if (row.Current)
{
var propVariations = (ContentVariation)row.Variations;
//if this prop doesn't vary but the row has a lang assigned or vice versa, flag this as not edited
if (!propVariations.VariesByCulture() && row.LanguageId.HasValue
|| propVariations.VariesByCulture() && !row.LanguageId.HasValue)
{
//Flag this as not edited for this node/lang if the key doesn't exist
if (!editedLanguageVersions.TryGetValue((row.NodeId, row.LanguageId), out _))
editedLanguageVersions.Add((row.NodeId, row.LanguageId), false);
//mark as false if the item doesn't exist, else coerce to true
editedDocument[row.NodeId] = editedDocument.TryGetValue(row.NodeId, out var edited) ? (edited |= false) : false;
}
else if (pubRow == null)
{
//this would mean that that this property is 'edited' since there is no published version
editedLanguageVersions[(row.NodeId, row.LanguageId)] = true;
editedDocument[row.NodeId] = true;
}
//compare the property values, if they differ from versions then flag the current version as edited
else if (IsPropertyValueChanged(pubRow, row))
{
//Here we would check if the property is invariant, in which case the edited language should be indicated by the default lang
editedLanguageVersions[(row.NodeId, !propVariations.VariesByCulture() ? defaultLang : row.LanguageId)] = true;
editedDocument[row.NodeId] = true;
}
//reset
pubRow = null;
}
}
//lookup all matching rows in umbracoDocumentCultureVariation
var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000)
.SelectMany(_ => Database.Fetch<DocumentCultureVariationDto>(
Sql().Select<DocumentCultureVariationDto>().From<DocumentCultureVariationDto>()
.WhereIn<DocumentCultureVariationDto>(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList())
.WhereIn<DocumentCultureVariationDto>(x => x.NodeId, editedLanguageVersions.Keys.Select(x => x.nodeId))))
//convert to dictionary with the same key type
.ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x);
var toUpdate = new List<DocumentCultureVariationDto>();
foreach (var ev in editedLanguageVersions)
{
if (docCultureVariationsToUpdate.TryGetValue(ev.Key, out var docVariations))
{
//check if it needs updating
if (docVariations.Edited != ev.Value)
{
docVariations.Edited = ev.Value;
toUpdate.Add(docVariations);
}
}
else if (ev.Key.langId.HasValue)
{
//This should never happen! If a property culture is flagged as edited then the culture must exist at the document level
throw new PanicException($"The existing DocumentCultureVariationDto was not found for node {ev.Key.nodeId} and language {ev.Key.langId}");
}
}
//Now bulk update the table DocumentCultureVariationDto, once for edited = true, another for edited = false
foreach (var editValue in toUpdate.GroupBy(x => x.Edited))
{
Database.Execute(Sql().Update<DocumentCultureVariationDto>(u => u.Set(x => x.Edited, editValue.Key))
.WhereIn<DocumentCultureVariationDto>(x => x.Id, editValue.Select(x => x.Id)));
}
//Now bulk update the umbracoDocument table
foreach (var editValue in editedDocument.GroupBy(x => x.Value))
{
Database.Execute(Sql().Update<DocumentDto>(u => u.Set(x => x.Edited, editValue.Key))
.WhereIn<DocumentDto>(x => x.NodeId, editValue.Select(x => x.Key)));
}
}
private static bool IsPropertyValueChanged(PropertyValueVersionDto pubRow, PropertyValueVersionDto row)
{
return !pubRow.TextValue.IsNullOrWhiteSpace() && pubRow.TextValue != row.TextValue
|| !pubRow.VarcharValue.IsNullOrWhiteSpace() && pubRow.VarcharValue != row.VarcharValue
|| pubRow.DateValue.HasValue && pubRow.DateValue != row.DateValue
|| pubRow.DecimalValue.HasValue && pubRow.DecimalValue != row.DecimalValue
|| pubRow.IntValue.HasValue && pubRow.IntValue != row.IntValue;
}
private class NameCompareDto
{
public int NodeId { get; set; }
public int CurrentVersion { get; set; }
public int LanguageId { get; set; }
public string CurrentName { get; set; }
public string PublishedName { get; set; }
public int? PublishedVersion { get; set; }
public int Id { get; set; } // the Id of the DocumentCultureVariationDto
public bool Edited { get; set; }
}
private class PropertyValueVersionDto
{
public int VersionId { get; set; }
public int PropertyTypeId { get; set; }
public int? LanguageId { get; set; }
public string Segment { get; set; }
public int? IntValue { get; set; }
private decimal? _decimalValue;
[Column("decimalValue")]
public decimal? DecimalValue
{
get => _decimalValue;
set => _decimalValue = value?.Normalize();
}
public DateTime? DateValue { get; set; }
public string VarcharValue { get; set; }
public string TextValue { get; set; }
public int NodeId { get; set; }
public bool Current { get; set; }
public bool Published { get; set; }
public byte Variations { get; set; }
}
private void DeletePropertyType(int contentTypeId, int propertyTypeId)

View File

@@ -279,6 +279,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return moveInfo;
}
public IReadOnlyDictionary<Udi, IEnumerable<string>> FindUsages(int id)
{
if (id == default)
return new Dictionary<Udi, IEnumerable<string>>();
var sql = Sql()
.Select<ContentTypeDto>(ct => ct.Select(node => node.NodeDto))
.AndSelect<PropertyTypeDto>(pt => Alias(pt.Alias, "ptAlias"), pt => Alias(pt.Name, "ptName"))
.From<PropertyTypeDto>()
.InnerJoin<ContentTypeDto>().On<ContentTypeDto, PropertyTypeDto>(ct => ct.NodeId, pt => pt.ContentTypeId)
.InnerJoin<NodeDto>().On<NodeDto, ContentTypeDto>(n => n.NodeId, ct => ct.NodeId)
.Where<PropertyTypeDto>(pt => pt.DataTypeId == id)
.OrderBy<NodeDto>(node => node.NodeId)
.AndBy<PropertyTypeDto>(pt => pt.Alias);
var dtos = Database.FetchOneToMany<ContentTypeReferenceDto>(ct => ct.PropertyTypes, sql);
return dtos.ToDictionary(
x => (Udi)new GuidUdi(ObjectTypes.GetUdiType(x.NodeDto.NodeObjectType.Value), x.NodeDto.UniqueId).EnsureClosed(),
x => (IEnumerable<string>)x.PropertyTypes.Select(p => p.Alias).ToList());
}
private string EnsureUniqueNodeName(string nodeName, int id = 0)
{
var template = SqlContext.Templates.Get("Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName", tsql => tsql
@@ -291,5 +313,24 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return SimilarNodeName.GetUniqueName(names, id, nodeName);
}
[TableName(Constants.DatabaseSchema.Tables.ContentType)]
private class ContentTypeReferenceDto : ContentTypeDto
{
[ResultColumn]
[Reference(ReferenceType.Many)]
public List<PropertyTypeReferenceDto> PropertyTypes { get; set; }
}
[TableName(Constants.DatabaseSchema.Tables.PropertyType)]
private class PropertyTypeReferenceDto
{
[Column("ptAlias")]
public string Alias { get; set; }
[Column("ptName")]
public string Name { get; set; }
}
}
}

View File

@@ -248,14 +248,63 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return dto == null ? null : MapDtoToContent(dto);
}
// deletes a specific version
public override void DeleteVersion(int versionId)
{
// TODO: test object node type?
// get the version we want to delete
var template = SqlContext.Templates.Get("Umbraco.Core.DocumentRepository.GetVersion", tsql =>
tsql.Select<ContentVersionDto>()
.AndSelect<DocumentVersionDto>()
.From<ContentVersionDto>()
.InnerJoin<DocumentVersionDto>()
.On<ContentVersionDto, DocumentVersionDto>((c, d) => c.Id == d.Id)
.Where<ContentVersionDto>(x => x.Id == SqlTemplate.Arg<int>("versionId"))
);
var versionDto = Database.Fetch<DocumentVersionDto>(template.Sql(new { versionId })).FirstOrDefault();
// nothing to delete
if (versionDto == null)
return;
// don't delete the current or published version
if (versionDto.ContentVersionDto.Current)
throw new InvalidOperationException("Cannot delete the current version.");
else if (versionDto.Published)
throw new InvalidOperationException("Cannot delete the published version.");
PerformDeleteVersion(versionDto.ContentVersionDto.NodeId, versionId);
}
// deletes all versions of an entity, older than a date.
public override void DeleteVersions(int nodeId, DateTime versionDate)
{
// TODO: test object node type?
// get the versions we want to delete, excluding the current one
var template = SqlContext.Templates.Get("Umbraco.Core.DocumentRepository.GetVersions", tsql =>
tsql.Select<ContentVersionDto>()
.From<ContentVersionDto>()
.InnerJoin<DocumentVersionDto>()
.On<ContentVersionDto, DocumentVersionDto>((c, d) => c.Id == d.Id)
.Where<ContentVersionDto>(x => x.NodeId == SqlTemplate.Arg<int>("nodeId") && !x.Current && x.VersionDate < SqlTemplate.Arg<DateTime>("versionDate"))
.Where<DocumentVersionDto>( x => !x.Published)
);
var versionDtos = Database.Fetch<ContentVersionDto>(template.Sql(new { nodeId, versionDate }));
foreach (var versionDto in versionDtos)
PerformDeleteVersion(versionDto.NodeId, versionDto.Id);
}
protected override void PerformDeleteVersion(int id, int versionId)
{
// raise event first else potential FK issues
OnUowRemovingVersion(new ScopedVersionEventArgs(AmbientScope, id, versionId));
Database.Delete<PropertyDataDto>("WHERE versionId = @versionId", new { versionId });
Database.Delete<ContentVersionDto>("WHERE id = @versionId", new { versionId });
Database.Delete<ContentVersionCultureVariationDto>("WHERE versionId = @versionId", new { versionId });
Database.Delete<DocumentVersionDto>("WHERE id = @versionId", new { versionId });
Database.Delete<ContentVersionDto>("WHERE id = @versionId", new { versionId });
}
#endregion
@@ -386,7 +435,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
// insert document variations
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
}
// refresh content
@@ -511,7 +560,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id
documentVersionDto.Published = false; // non-published version
Database.Insert(documentVersionDto);
Database.Insert(documentVersionDto);
}
// replace the property data (rather than updating)
@@ -571,7 +620,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
// insert document variations
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, publishing, editedCultures));
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
}
// refresh content
@@ -1297,25 +1346,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
};
}
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, bool publishing, HashSet<string> editedCultures)
private IEnumerable<DocumentCultureVariationDto> GetDocumentVariationDtos(IContent content, HashSet<string> editedCultures)
{
var allCultures = content.AvailableCultures.Union(content.PublishedCultures); // union = distinct
foreach (var culture in allCultures)
yield return new DocumentCultureVariationDto
{
var dto = new DocumentCultureVariationDto
{
NodeId = content.Id,
LanguageId = LanguageRepository.GetIdByIsoCode(culture) ?? throw new InvalidOperationException("Not a valid culture."),
Culture = culture,
Name = content.GetCultureName(culture) ?? content.GetPublishName(culture),
// note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
Available = content.IsCultureAvailable(culture),
Published = content.IsCulturePublished(culture),
// note: can't use IsCultureEdited at that point - hasn't been updated yet - see PersistUpdatedItem
Edited = content.IsCultureAvailable(culture) &&
(!content.IsCulturePublished(culture) || (editedCultures != null && editedCultures.Contains(culture)))
};
yield return dto;
}
}
private class ContentVariation

View File

@@ -42,8 +42,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint;
var isMedia = objectType == Constants.ObjectTypes.Media;
var isMember = objectType == Constants.ObjectTypes.Member;
var sql = GetBaseWhere(isContent, isMedia, false, x =>
var sql = GetBaseWhere(isContent, isMedia, isMember, false, x =>
{
if (filter == null) return;
foreach (var filterClause in filter.GetWhereClauses())
@@ -54,7 +55,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var translator = new SqlTranslator<IUmbracoEntity>(sql, query);
sql = translator.Translate();
sql = AddGroupBy(isContent, isMedia, sql, ordering.IsEmpty);
sql = AddGroupBy(isContent, isMedia, isMember, sql, ordering.IsEmpty);
if (!ordering.IsEmpty)
{
@@ -81,6 +82,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
dtos = page.Items;
totalRecords = page.TotalItems;
}
else if (isMember)
{
var page = Database.Page<MemberEntityDto>(pageIndexToFetch, pageSize, sql);
dtos = page.Items;
totalRecords = page.TotalItems;
}
else
{
var page = Database.Page<BaseDto>(pageIndexToFetch, pageSize, sql);
@@ -88,7 +95,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
totalRecords = page.TotalItems;
}
var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray();
var entities = dtos.Select(x => BuildEntity(isContent, isMedia, isMember, x)).ToArray();
if (isContent)
BuildVariants(entities.Cast<DocumentEntitySlim>());
@@ -98,13 +105,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public IEntitySlim Get(Guid key)
{
var sql = GetBaseWhere(false, false, false, key);
var sql = GetBaseWhere(false, false, false, false, key);
var dto = Database.FirstOrDefault<BaseDto>(sql);
return dto == null ? null : BuildEntity(false, false, dto);
return dto == null ? null : BuildEntity(false, false, false, dto);
}
private IEntitySlim GetEntity(Sql<ISqlContext> sql, bool isContent, bool isMedia)
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
if (isContent)
@@ -120,7 +127,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (dto == null) return null;
var entity = BuildEntity(false, isMedia, dto);
var entity = BuildEntity(false, isMedia, isMember, dto);
return entity;
}
@@ -129,25 +136,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var isContent = objectTypeId == Constants.ObjectTypes.Document || objectTypeId == Constants.ObjectTypes.DocumentBlueprint;
var isMedia = objectTypeId == Constants.ObjectTypes.Media;
var isMember = objectTypeId == Constants.ObjectTypes.Member;
var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, key);
return GetEntity(sql, isContent, isMedia);
var sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectTypeId, key);
return GetEntity(sql, isContent, isMedia, isMember);
}
public IEntitySlim Get(int id)
{
var sql = GetBaseWhere(false, false, false, id);
var sql = GetBaseWhere(false, false, false, false, id);
var dto = Database.FirstOrDefault<BaseDto>(sql);
return dto == null ? null : BuildEntity(false, false, dto);
return dto == null ? null : BuildEntity(false, false, false, dto);
}
public IEntitySlim Get(int id, Guid objectTypeId)
{
var isContent = objectTypeId == Constants.ObjectTypes.Document || objectTypeId == Constants.ObjectTypes.DocumentBlueprint;
var isMedia = objectTypeId == Constants.ObjectTypes.Media;
var isMember = objectTypeId == Constants.ObjectTypes.Member;
var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, id);
return GetEntity(sql, isContent, isMedia);
var sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectTypeId, id);
return GetEntity(sql, isContent, isMedia, isMember);
}
public IEnumerable<IEntitySlim> GetAll(Guid objectType, params int[] ids)
@@ -164,7 +173,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
: PerformGetAll(objectType);
}
private IEnumerable<IEntitySlim> GetEntities(Sql<ISqlContext> sql, bool isContent, bool isMedia)
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
if (isContent)
@@ -180,7 +189,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
? (IEnumerable<BaseDto>)Database.Fetch<MediaEntityDto>(sql)
: Database.Fetch<BaseDto>(sql);
var entities = dtos.Select(x => BuildEntity(false, isMedia, x)).ToArray();
var entities = dtos.Select(x => BuildEntity(false, isMedia, isMember, x)).ToArray();
return entities;
}
@@ -189,9 +198,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint;
var isMedia = objectType == Constants.ObjectTypes.Media;
var isMember = objectType == Constants.ObjectTypes.Member;
var sql = GetFullSqlForEntityType(isContent, isMedia, objectType, filter);
return GetEntities(sql, isContent, isMedia);
var sql = GetFullSqlForEntityType(isContent, isMedia, isMember, objectType, filter);
return GetEntities(sql, isContent, isMedia, isMember);
}
public IEnumerable<TreeEntityPath> GetAllPaths(Guid objectType, params int[] ids)
@@ -218,26 +228,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query)
{
var sqlClause = GetBase(false, false, null);
var sqlClause = GetBase(false, false, false, null);
var translator = new SqlTranslator<IUmbracoEntity>(sqlClause, query);
var sql = translator.Translate();
sql = AddGroupBy(false, false, sql, true);
sql = AddGroupBy(false, false, false, sql, true);
var dtos = Database.Fetch<BaseDto>(sql);
return dtos.Select(x => BuildEntity(false, false, x)).ToList();
return dtos.Select(x => BuildEntity(false, false, false, x)).ToList();
}
public IEnumerable<IEntitySlim> GetByQuery(IQuery<IUmbracoEntity> query, Guid objectType)
{
var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint;
var isMedia = objectType == Constants.ObjectTypes.Media;
var isMember = objectType == Constants.ObjectTypes.Member;
var sql = GetBaseWhere(isContent, isMedia, false, null, objectType);
var sql = GetBaseWhere(isContent, isMedia, isMember, false, null, objectType);
var translator = new SqlTranslator<IUmbracoEntity>(sql, query);
sql = translator.Translate();
sql = AddGroupBy(isContent, isMedia, sql, true);
sql = AddGroupBy(isContent, isMedia, isMember, sql, true);
return GetEntities(sql, isContent, isMedia);
return GetEntities(sql, isContent, isMedia, isMember);
}
public UmbracoObjectTypes GetObjectType(int id)
@@ -329,29 +340,29 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
// gets the full sql for a given object type and a given unique id
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, Guid uniqueId)
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Guid uniqueId)
{
var sql = GetBaseWhere(isContent, isMedia, false, objectType, uniqueId);
return AddGroupBy(isContent, isMedia, sql, true);
var sql = GetBaseWhere(isContent, isMedia, isMember, false, objectType, uniqueId);
return AddGroupBy(isContent, isMedia, isMember, sql, true);
}
// gets the full sql for a given object type and a given node id
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, int nodeId)
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, int nodeId)
{
var sql = GetBaseWhere(isContent, isMedia, false, objectType, nodeId);
return AddGroupBy(isContent, isMedia, sql, true);
var sql = GetBaseWhere(isContent, isMedia, isMember, false, objectType, nodeId);
return AddGroupBy(isContent, isMedia, isMember, sql, true);
}
// gets the full sql for a given object type, with a given filter
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, Action<Sql<ISqlContext>> filter)
protected Sql<ISqlContext> GetFullSqlForEntityType(bool isContent, bool isMedia, bool isMember, Guid objectType, Action<Sql<ISqlContext>> filter)
{
var sql = GetBaseWhere(isContent, isMedia, false, filter, objectType);
return AddGroupBy(isContent, isMedia, sql, true);
var sql = GetBaseWhere(isContent, isMedia, isMember, false, filter, objectType);
return AddGroupBy(isContent, isMedia, isMember, sql, true);
}
// gets the base SELECT + FROM [+ filter] sql
// always from the 'current' content version
protected Sql<ISqlContext> GetBase(bool isContent, bool isMedia, Action<Sql<ISqlContext>> filter, bool isCount = false)
protected Sql<ISqlContext> GetBase(bool isContent, bool isMedia, bool isMember, Action<Sql<ISqlContext>> filter, bool isCount = false)
{
var sql = Sql();
@@ -366,7 +377,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
.AndSelect<NodeDto>(x => x.SortOrder, x => x.UniqueId, x => x.Text, x => x.NodeObjectType, x => x.CreateDate)
.Append(", COUNT(child.id) AS children");
if (isContent || isMedia)
if (isContent || isMedia || isMember)
sql
.AndSelect<ContentVersionDto>(x => Alias(x.Id, "versionId"))
.AndSelect<ContentTypeDto>(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer, x => x.Variations);
@@ -387,7 +398,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
sql
.From<NodeDto>();
if (isContent || isMedia)
if (isContent || isMedia || isMember)
{
sql
.InnerJoin<ContentVersionDto>().On<NodeDto, ContentVersionDto>((left, right) => left.NodeId == right.NodeId && right.Current)
@@ -404,7 +415,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (isMedia)
{
sql
.InnerJoin<MediaVersionDto>().On<ContentVersionDto, MediaVersionDto>((left, right) => left.Id == right.Id);
.LeftJoin<MediaVersionDto>().On<ContentVersionDto, MediaVersionDto>((left, right) => left.Id == right.Id);
}
//Any LeftJoin statements need to come last
@@ -422,49 +433,49 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// gets the base SELECT + FROM [+ filter] + WHERE sql
// for a given object type, with a given filter
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, Action<Sql<ISqlContext>> filter, Guid objectType)
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Action<Sql<ISqlContext>> filter, Guid objectType)
{
return GetBase(isContent, isMedia, filter, isCount)
return GetBase(isContent, isMedia, isMember, filter, isCount)
.Where<NodeDto>(x => x.NodeObjectType == objectType);
}
// gets the base SELECT + FROM + WHERE sql
// for a given node id
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, int id)
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, int id)
{
var sql = GetBase(isContent, isMedia, null, isCount)
var sql = GetBase(isContent, isMedia, isMember, null, isCount)
.Where<NodeDto>(x => x.NodeId == id);
return AddGroupBy(isContent, isMedia, sql, true);
return AddGroupBy(isContent, isMedia, isMember, sql, true);
}
// gets the base SELECT + FROM + WHERE sql
// for a given unique id
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid uniqueId)
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Guid uniqueId)
{
var sql = GetBase(isContent, isMedia, null, isCount)
var sql = GetBase(isContent, isMedia, isMember, null, isCount)
.Where<NodeDto>(x => x.UniqueId == uniqueId);
return AddGroupBy(isContent, isMedia, sql, true);
return AddGroupBy(isContent, isMedia, isMember, sql, true);
}
// gets the base SELECT + FROM + WHERE sql
// for a given object type and node id
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid objectType, int nodeId)
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Guid objectType, int nodeId)
{
return GetBase(isContent, isMedia, null, isCount)
return GetBase(isContent, isMedia, isMember, null, isCount)
.Where<NodeDto>(x => x.NodeId == nodeId && x.NodeObjectType == objectType);
}
// gets the base SELECT + FROM + WHERE sql
// for a given object type and unique id
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid objectType, Guid uniqueId)
protected Sql<ISqlContext> GetBaseWhere(bool isContent, bool isMedia, bool isMember, bool isCount, Guid objectType, Guid uniqueId)
{
return GetBase(isContent, isMedia, null, isCount)
return GetBase(isContent, isMedia, isMember, null, isCount)
.Where<NodeDto>(x => x.UniqueId == uniqueId && x.NodeObjectType == objectType);
}
// gets the GROUP BY / ORDER BY sql
// required in order to count children
protected Sql<ISqlContext> AddGroupBy(bool isContent, bool isMedia, Sql<ISqlContext> sql, bool defaultSort)
protected Sql<ISqlContext> AddGroupBy(bool isContent, bool isMedia, bool isMember, Sql<ISqlContext> sql, bool defaultSort)
{
sql
.GroupBy<NodeDto>(x => x.NodeId, x => x.Trashed, x => x.ParentId, x => x.UserId, x => x.Level, x => x.Path)
@@ -483,7 +494,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
if (isContent || isMedia)
if (isContent || isMedia || isMember)
sql
.AndBy<ContentVersionDto>(x => x.Id)
.AndBy<ContentTypeDto>(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer, x => x.Variations);
@@ -528,6 +539,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
public string MediaPath { get; set; }
}
private class MemberEntityDto : BaseDto
{
}
public class VariantInfoDto
{
public int NodeId { get; set; }
@@ -574,12 +589,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
#region Factory
private EntitySlim BuildEntity(bool isContent, bool isMedia, BaseDto dto)
private EntitySlim BuildEntity(bool isContent, bool isMedia, bool isMember, BaseDto dto)
{
if (isContent)
return BuildDocumentEntity(dto);
if (isMedia)
return BuildMediaEntity(dto);
if (isMember)
return BuildMemberEntity(dto);
// EntitySlim does not track changes
var entity = new EntitySlim();
@@ -644,6 +661,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return entity;
}
private MemberEntitySlim BuildMemberEntity(BaseDto dto)
{
// EntitySlim does not track changes
var entity = new MemberEntitySlim();
BuildEntity(entity, dto);
entity.ContentTypeAlias = dto.Alias;
entity.ContentTypeIcon = dto.Icon;
entity.ContentTypeThumbnail = dto.Thumbnail;
return entity;
}
#endregion
}
}

View File

@@ -102,17 +102,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override IEnumerable<string> GetDeleteClauses()
{
var list = new List<string>
{
//NOTE: There is no constraint between the Language and cmsDictionary/cmsLanguageText tables (?)
// but we still need to remove them
"DELETE FROM cmsLanguageText WHERE languageId = @id",
"DELETE FROM umbracoPropertyData WHERE languageId = @id",
"DELETE FROM umbracoContentVersionCultureVariation WHERE languageId = @id",
"DELETE FROM umbracoDocumentCultureVariation WHERE languageId = @id",
"DELETE FROM umbracoLanguage WHERE id = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.Tag + " WHERE languageId = @id"
"DELETE FROM " + Constants.DatabaseSchema.Tables.DictionaryValue + " WHERE languageId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.PropertyData + " WHERE languageId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.ContentVersionCultureVariation + " WHERE languageId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.DocumentCultureVariation + " WHERE languageId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.TagRelationship + " WHERE tagId IN (SELECT id FROM " + Constants.DatabaseSchema.Tables.Tag + " WHERE languageId = @id)",
"DELETE FROM " + Constants.DatabaseSchema.Tables.Tag + " WHERE languageId = @id",
"DELETE FROM " + Constants.DatabaseSchema.Tables.Language + " WHERE id = @id"
};
return list;
}
@@ -182,6 +182,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
throw new InvalidOperationException($"Cannot save the default language ({entity.IsoCode}) as non-default. Make another language the default language instead.");
}
if (entity.IsPropertyDirty(nameof(ILanguage.IsoCode)))
{
//if the iso code is changing, ensure there's not another lang with the same code already assigned
var sameCode = Sql()
.SelectCount()
.From<LanguageDto>()
.Where<LanguageDto>(x => x.IsoCode == entity.IsoCode && x.Id != entity.Id);
var countOfSameCode = Database.ExecuteScalar<int>(sameCode);
if (countOfSameCode > 0)
throw new InvalidOperationException($"Cannot update the language to a new culture: {entity.IsoCode} since that culture is already assigned to another language entity.");
}
// fallback cycles are detected at service level
// update
@@ -250,7 +263,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
lock (_codeIdMap)
{
if (_codeIdMap.TryGetValue(isoCode, out var id)) return id;
if (isoCode.Contains('-') && _codeIdMap.TryGetValue(isoCode.Split('-').First(), out var invariantId)) return invariantId;
}
if (throwOnNotFound)
throw new ArgumentException($"Code {isoCode} does not correspond to an existing language.", nameof(isoCode));

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -17,8 +18,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </summary>
internal class MediaTypeRepository : ContentTypeRepositoryBase<IMediaType>, IMediaTypeRepository
{
public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
: base(scopeAccessor, cache, logger, commonRepository)
public MediaTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => MediaType.SupportsPublishingConst;
@@ -50,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
if (ids.Any()) throw new Exception("panic");
if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType<IMediaType>();
}

View File

@@ -133,7 +133,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// joining the type so we can do a query against the member type - not sure if this adds much overhead or not?
// the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content
// types by default on the document and media repo's so we can query by content type there too.
// types by default on the document and media repos so we can query by content type there too.
.InnerJoin<ContentTypeDto>().On<ContentDto, ContentTypeDto>(left => left.ContentTypeId, right => right.NodeId);
sql.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
@@ -546,6 +546,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (ordering.OrderBy.InvariantEquals("userName"))
return SqlSyntax.GetFieldName<MemberDto>(x => x.LoginName);
if (ordering.OrderBy.InvariantEquals("updateDate"))
return SqlSyntax.GetFieldName<ContentVersionDto>(x => x.VersionDate);
if (ordering.OrderBy.InvariantEquals("createDate"))
return SqlSyntax.GetFieldName<NodeDto>(x => x.CreateDate);
if (ordering.OrderBy.InvariantEquals("contentTypeAlias"))
return SqlSyntax.GetFieldName<ContentTypeDto>(x => x.Alias);
return base.ApplySystemOrdering(ref sql, ordering);
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
@@ -18,8 +19,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </summary>
internal class MemberTypeRepository : ContentTypeRepositoryBase<IMemberType>, IMemberTypeRepository
{
public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository)
: base(scopeAccessor, cache, logger, commonRepository)
public MemberTypeRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IContentTypeCommonRepository commonRepository, ILanguageRepository languageRepository)
: base(scopeAccessor, cache, logger, commonRepository, languageRepository)
{ }
protected override bool SupportsPublishing => MemberType.SupportsPublishingConst;
@@ -57,7 +58,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
{
// the cache policy will always want everything
// even GetMany(ids) gets everything and filters afterwards
if (ids.Any()) throw new Exception("panic");
if (ids.Any()) throw new PanicException("There can be no ids specified");
return CommonRepository.GetAllTypes().OfType<IMemberType>();
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
@@ -105,7 +106,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID");
CreateDateUtc = redirectUrl.CreateDateUtc,
Url = redirectUrl.Url,
Culture = redirectUrl.Culture,
UrlHash = redirectUrl.Url.ToSHA1()
UrlHash = redirectUrl.Url.GenerateHash<SHA1>()
};
}
@@ -134,7 +135,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID");
public IRedirectUrl Get(string url, Guid contentKey, string culture)
{
var urlHash = url.ToSHA1();
var urlHash = url.GenerateHash<SHA1>();
var sql = GetBaseQuery(false).Where<RedirectUrlDto>(x => x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey && x.Culture == culture);
var dto = Database.Fetch<RedirectUrlDto>(sql).FirstOrDefault();
return dto == null ? null : Map(dto);
@@ -157,7 +158,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID");
public IRedirectUrl GetMostRecentUrl(string url)
{
var urlHash = url.ToSHA1();
var urlHash = url.GenerateHash<SHA1>();
var sql = GetBaseQuery(false)
.Where<RedirectUrlDto>(x => x.Url == url && x.UrlHash == urlHash)
.OrderByDescending<RedirectUrlDto>(x => x.CreateDateUtc);

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Text.RegularExpressions;
using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations;
@@ -76,6 +77,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax
string ConvertIntegerToOrderableString { get; }
string ConvertDateToOrderableString { get; }
string ConvertDecimalToOrderableString { get; }
/// <summary>
/// Returns the default isolation level for the database
/// </summary>
IsolationLevel DefaultIsolationLevel { get; }
IEnumerable<string> GetTablesInSchema(IDatabase db);
IEnumerable<ColumnInfo> GetColumnsInSchema(IDatabase db);
@@ -121,5 +127,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// unspecified.</para>
/// </remarks>
bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName);
void ReadLock(IDatabase db, params int[] lockIds);
void WriteLock(IDatabase db, params int[] lockIds);
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlServerCe;
using System.Linq;
using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations;
@@ -52,6 +54,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return "(" + string.Join("+", args) + ")";
}
public override System.Data.IsolationLevel DefaultIsolationLevel => System.Data.IsolationLevel.RepeatableRead;
public override string FormatColumnRename(string tableName, string oldName, string newName)
{
//NOTE Sql CE doesn't support renaming a column, so a new column needs to be created, then copy data and finally remove old column
@@ -152,6 +156,39 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault()
return result > 0;
}
public override void WriteLock(IDatabase db, params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
db.Execute(@"SET LOCK_TIMEOUT 1800;");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
var i = db.Execute(@"UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
if (i == 0) // ensure we are actually locking!
throw new ArgumentException($"LockObject with id={lockId} does not exist.");
}
}
public override void ReadLock(IDatabase db, params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
var i = db.ExecuteScalar<int?>("SELECT value FROM umbracoLock WHERE id=@id", new { id = lockId });
if (i == null) // ensure we are actually locking!
throw new ArgumentException($"LockObject with id={lockId} does not exist.");
}
}
protected override string FormatIdentity(ColumnDefinition column)
{
return column.IsIdentity ? GetIdentityString(column) : string.Empty;

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using NPoco;
using Umbraco.Core.Logging;
@@ -179,6 +180,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return items.Select(x => x.TABLE_NAME).Cast<string>().ToList();
}
public override IsolationLevel DefaultIsolationLevel => IsolationLevel.ReadCommitted;
public override IEnumerable<ColumnInfo> GetColumnsInSchema(IDatabase db)
{
var items = db.Fetch<dynamic>("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
@@ -246,6 +249,41 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
return result > 0;
}
public override void WriteLock(IDatabase db, params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
db.Execute(@"SET LOCK_TIMEOUT 1800;");
var i = db.Execute(@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
if (i == 0) // ensure we are actually locking!
throw new ArgumentException($"LockObject with id={lockId} does not exist.");
}
}
public override void ReadLock(IDatabase db, params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
var i = db.ExecuteScalar<int?>("SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id", new { id = lockId });
if (i == null) // ensure we are actually locking!
throw new ArgumentException($"LockObject with id={lockId} does not exist.", nameof(lockIds));
}
}
public override string FormatColumnRename(string tableName, string oldName, string newName)
{
return string.Format(RenameColumn, tableName, oldName, newName);

View File

@@ -200,7 +200,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return "NVARCHAR";
}
public abstract IsolationLevel DefaultIsolationLevel { get; }
public virtual IEnumerable<string> GetTablesInSchema(IDatabase db)
{
return new List<string>();
@@ -225,6 +227,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
public abstract bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName);
public abstract void ReadLock(IDatabase db, params int[] lockIds);
public abstract void WriteLock(IDatabase db, params int[] lockIds);
public virtual bool DoesTableExist(IDatabase db, string tableName)
{
return false;

View File

@@ -20,9 +20,6 @@ namespace Umbraco.Core.Persistence
/// </remarks>
public class UmbracoDatabase : Database, IUmbracoDatabase
{
// Umbraco's default isolation level is RepeatableRead
private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead;
private readonly ILogger _logger;
private readonly RetryPolicy _connectionRetryPolicy;
private readonly RetryPolicy _commandRetryPolicy;
@@ -38,7 +35,7 @@ namespace Umbraco.Core.Persistence
/// <para>Also used by DatabaseBuilder for creating databases and installing/upgrading.</para>
/// </remarks>
public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger logger, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null)
: base(connectionString, sqlContext.DatabaseType, provider, DefaultIsolationLevel)
: base(connectionString, sqlContext.DatabaseType, provider, sqlContext.SqlSyntax.DefaultIsolationLevel)
{
SqlContext = sqlContext;
@@ -54,7 +51,7 @@ namespace Umbraco.Core.Persistence
/// </summary>
/// <remarks>Internal for unit tests only.</remarks>
internal UmbracoDatabase(DbConnection connection, ISqlContext sqlContext, ILogger logger)
: base(connection, sqlContext.DatabaseType, DefaultIsolationLevel)
: base(connection, sqlContext.DatabaseType, sqlContext.SqlSyntax.DefaultIsolationLevel)
{
SqlContext = sqlContext;
_logger = logger;