diff --git a/src/Umbraco.Core/Constants-ObjectTypes.cs b/src/Umbraco.Core/Constants-ObjectTypes.cs index 4a79437c61..a1e6e55ee9 100644 --- a/src/Umbraco.Core/Constants-ObjectTypes.cs +++ b/src/Umbraco.Core/Constants-ObjectTypes.cs @@ -222,6 +222,16 @@ namespace Umbraco.Core /// Guid for a Forms DataSource. /// public static readonly Guid LanguageGuid = new Guid(Language); + + /// + /// Guid for an Identifier Reservation. + /// + public const string IdReservation = "92849B1E-3904-4713-9356-F646F87C25F4"; + + /// + /// Guid for an Identifier Reservation. + /// + public static readonly Guid IdReservationGuid = new Guid(IdReservation); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs index d0136a10a4..d4caaef768 100644 --- a/src/Umbraco.Core/Models/UmbracoObjectTypes.cs +++ b/src/Umbraco.Core/Models/UmbracoObjectTypes.cs @@ -185,6 +185,13 @@ namespace Umbraco.Core.Models /// [UmbracoObjectType(Constants.ObjectTypes.Language)] [FriendlyName("Language")] - Language + Language, + + /// + /// Reserved Identifier + /// + [UmbracoObjectType(Constants.ObjectTypes.IdReservation)] + [FriendlyName("Identifier Reservation")] + IdReservation } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index eac9d197be..a01cbaa2e8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -435,7 +435,24 @@ namespace Umbraco.Core.Persistence.Repositories nodeDto.Path = parent.Path; nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); nodeDto.SortOrder = sortOrder; - var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); + + // note: + // there used to be a check on Database.IsNew(nodeDto) here to either Insert or Update, + // but I cannot figure out what was the point, as the node should obviously be new if + // we reach that point - removed. + + // see if there's a reserved identifier for this unique id + var sql = new Sql("SELECT id FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", nodeDto.UniqueId, Constants.ObjectTypes.IdReservationGuid); + var id = Database.ExecuteScalar(sql); + if (id > 0) + { + nodeDto.NodeId = id; + Database.Update(nodeDto); + } + else + { + Database.Insert(nodeDto); + } //Update with new correct path nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 598b16d78d..b22e8981b9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -84,7 +84,7 @@ namespace Umbraco.Core.Persistence.Repositories #endregion #region Overrides of PetaPocoRepositoryBase - + protected override Sql GetBaseQuery(BaseQueryType queryType) { var sql = new Sql(); @@ -153,7 +153,7 @@ namespace Umbraco.Core.Persistence.Repositories /// This is the underlying method that processes most queries for this repository /// /// - /// The full SQL to select all media data + /// The full SQL to select all media data /// /// /// The Id SQL to just return all media ids - used to process the properties for the media item @@ -164,7 +164,7 @@ namespace Umbraco.Core.Persistence.Repositories { // fetch returns a list so it's ok to iterate it in this method var dtos = Database.Fetch(sqlFull); - + //This is a tuple list identifying if the content item came from the cache or not var content = new List>(); var defs = new DocumentDefinitionCollection(); @@ -180,7 +180,7 @@ namespace Umbraco.Core.Persistence.Repositories if (withCache) { var cached = IsolatedCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); - //only use this cached version if the dto returned is the same version - this is just a safety check, media doesn't + //only use this cached version if the dto returned is the same version - this is just a safety check, media doesn't //store different versions, but just in case someone corrupts some data we'll double check to be sure. if (cached != null && cached.Version == dto.VersionId) { @@ -303,7 +303,7 @@ namespace Umbraco.Core.Persistence.Repositories .From(SqlSyntax) .InnerJoin(SqlSyntax) .On(SqlSyntax, left => left.NodeId, right => right.NodeId); - + if (contentTypeIdsA.Length > 0) { xmlIdsQuery.InnerJoin(SqlSyntax) @@ -314,7 +314,7 @@ namespace Umbraco.Core.Persistence.Repositories } xmlIdsQuery.Where(dto => dto.NodeObjectType == mediaObjectType, SqlSyntax); - + var allXmlIds = Database.Fetch(xmlIdsQuery); var toRemove = allXmlIds.Except(allMediaIds).ToArray(); @@ -380,7 +380,24 @@ namespace Umbraco.Core.Persistence.Repositories nodeDto.Path = parent.Path; nodeDto.Level = short.Parse(level.ToString(CultureInfo.InvariantCulture)); nodeDto.SortOrder = sortOrder; - var o = Database.IsNew(nodeDto) ? Convert.ToInt32(Database.Insert(nodeDto)) : Database.Update(nodeDto); + + // note: + // there used to be a check on Database.IsNew(nodeDto) here to either Insert or Update, + // but I cannot figure out what was the point, as the node should obviously be new if + // we reach that point - removed. + + // see if there's a reserved identifier for this unique id + var sql = new Sql("SELECT id FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", nodeDto.UniqueId, Constants.ObjectTypes.IdReservationGuid); + var id = Database.ExecuteScalar(sql); + if (id > 0) + { + nodeDto.NodeId = id; + Database.Update(nodeDto); + } + else + { + Database.Insert(nodeDto); + } //Update with new correct path nodeDto.Path = string.Concat(parent.Path, ",", nodeDto.NodeId); @@ -564,7 +581,7 @@ namespace Umbraco.Core.Persistence.Repositories private IMedia CreateMediaFromDto(ContentVersionDto dto, Sql docSql) { var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId); - + var media = MediaFactory.BuildEntity(dto, contentType); var docDef = new DocumentDefinition(dto, contentType); @@ -584,7 +601,7 @@ namespace Umbraco.Core.Persistence.Repositories if (EnsureUniqueNaming == false) return nodeName; - var names = Database.Fetch("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId", + var names = Database.Fetch("SELECT id, text AS name FROM umbracoNode WHERE nodeObjectType=@objectType AND parentId=@parentId", new { objectType = NodeObjectTypeId, parentId }); return SimilarNodeName.GetUniqueName(names, id, nodeName); diff --git a/src/Umbraco.Core/Services/EntityService.cs b/src/Umbraco.Core/Services/EntityService.cs index 04d6c276b2..c10311dc01 100644 --- a/src/Umbraco.Core/Services/EntityService.cs +++ b/src/Umbraco.Core/Services/EntityService.cs @@ -646,5 +646,34 @@ namespace Umbraco.Core.Services return exists; } } + + /// + public int ReserveId(Guid key) + { + NodeDto node; + using (var scope = UowProvider.ScopeProvider.CreateScope()) + { + var sql = new Sql("SELECT * FROM umbracoNode WHERE uniqueID=@0 AND nodeObjectType=@1", key, Constants.ObjectTypes.IdReservationGuid); + node = scope.Database.SingleOrDefault(sql); + if (node != null) throw new InvalidOperationException("An identifier has already been reserved for this Udi."); + node = new NodeDto + { + UniqueId = key, + Text = "RESERVED.ID", + NodeObjectType = Constants.ObjectTypes.IdReservationGuid, + + CreateDate = DateTime.Now, + UserId = 0, + ParentId = -1, + Level = 1, + Path = "-1", + SortOrder = 0, + Trashed = false + }; + scope.Database.Insert(node); + scope.Complete(); + } + return node.NodeId; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs index 82e5227cf2..d1da84a1c8 100644 --- a/src/Umbraco.Core/Services/IEntityService.cs +++ b/src/Umbraco.Core/Services/IEntityService.cs @@ -170,7 +170,7 @@ namespace Umbraco.Core.Services /// /// /// - /// + /// /// IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes umbracoObjectType, long pageIndex, int pageSize, out long totalRecords, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); @@ -270,5 +270,13 @@ namespace Umbraco.Core.Services /// /// Type of the entity Type GetEntityType(UmbracoObjectTypes umbracoObjectType); + + /// + /// Reserves an identifier for a key. + /// + /// They key. + /// The identifier. + /// When a new content or a media is saved with the key, it will have the reserved identifier. + int ReserveId(Guid key); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/IdkMap.cs b/src/Umbraco.Core/Services/IdkMap.cs index 015bb104dc..411316fca2 100644 --- a/src/Umbraco.Core/Services/IdkMap.cs +++ b/src/Umbraco.Core/Services/IdkMap.cs @@ -46,6 +46,10 @@ namespace Umbraco.Core.Services if (val == null) return Attempt.Fail(); + // cache reservations, when something is saved this cache is cleared anyways + //if (umbracoObjectType == UmbracoObjectTypes.IdReservation) + // Attempt.Succeed(val.Value); + try { _locker.EnterWriteLock(); @@ -95,6 +99,10 @@ namespace Umbraco.Core.Services if (val == null) return Attempt.Fail(); + // cache reservations, when something is saved this cache is cleared anyways + //if (umbracoObjectType == UmbracoObjectTypes.IdReservation) + // Attempt.Succeed(val.Value); + try { _locker.EnterWriteLock();