Fix issues with added PanicException
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user