Merge pull request #2505 from umbraco/temp-u4-10756
U4-10756 - id/guid cache
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -22,13 +23,139 @@ namespace Umbraco.Core.Services
|
||||
// note - no need for uow, scope would be enough, but a pain to wire
|
||||
// note - for pure read-only we might want to *not* enforce a transaction?
|
||||
|
||||
// notes
|
||||
//
|
||||
// - this class assumes that the id/guid map is unique; that is, if an id and a guid map
|
||||
// to each other, then the id will never map to another guid, and the guid will never map
|
||||
// to another id
|
||||
//
|
||||
// - LeeK's solution in 7.7 was to look for the id/guid in the content cache "on demand" via
|
||||
// XPath, which is probably fast enough but cannot deal with media ids + it maintains a
|
||||
// separate, duplicate cache
|
||||
// see https://github.com/umbraco/Umbraco-CMS/pull/2398
|
||||
//
|
||||
// - Andy's solution in a package was to prefetch all by sql; it cannot prefecth reserved ids
|
||||
// as we don't know the corresponding object type, but that's not a big issue - but then we
|
||||
// have a full database query on startup
|
||||
// see https://github.com/AndyButland/UmbracoUdiToIdCache
|
||||
//
|
||||
// - the original IdkMap implementation that was used by services, did a database lookup on
|
||||
// each cache miss, which is fine enough for services, but would be really slow at content
|
||||
// cache level
|
||||
//
|
||||
// - cache is cleared by MediaCacheRefresher, UnpublishedPageCacheRefresher, and other
|
||||
// refreshers - because id/guid map is unique, we only clear to avoid leaking memory, 'cos
|
||||
// we don't risk caching obsolete values - and only when actually deleting
|
||||
//
|
||||
// so...
|
||||
//
|
||||
// - there's a single caching point, and it's idkMap
|
||||
// - there are no "helper methods" - the published content cache itself knows about Guids
|
||||
// - when the published content cache is instanciated, it populates the idkMap with what it knows
|
||||
// and it registers a way for the idkMap to look for id/keys in the published content cache
|
||||
// - we do NOT prefetch anything from database
|
||||
// - when a request comes in:
|
||||
// the published content cache uses the idkMap to map id/key
|
||||
// if the idkMap already knows about the map, it returns the value
|
||||
// else it tries the published cache via XPath
|
||||
// else it hits the database
|
||||
|
||||
|
||||
private readonly ConcurrentDictionary<UmbracoObjectTypes, (Func<int, Guid> id2key, Func<Guid, int> key2id)> _dictionary
|
||||
= new ConcurrentDictionary<UmbracoObjectTypes, (Func<int, Guid> id2key, Func<Guid, int> key2id)>();
|
||||
|
||||
internal void SetMapper(UmbracoObjectTypes umbracoObjectType, Func<int, Guid> id2key, Func<Guid, int> key2id)
|
||||
{
|
||||
_dictionary[umbracoObjectType] = (id2key, key2id);
|
||||
}
|
||||
|
||||
internal void Populate(IEnumerable<(int id, Guid key)> pairs, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
_id2Key.Add(pair.id, new TypedId<Guid>(pair.key, umbracoObjectType));
|
||||
_key2Id.Add(pair.key, new TypedId<int>(pair.id, umbracoObjectType));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_locker.IsWriteLockHeld)
|
||||
_locker.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
#if POPULATE_FROM_DATABASE
|
||||
private void PopulateLocked()
|
||||
{
|
||||
// don't if not empty
|
||||
if (_key2Id.Count > 0) return;
|
||||
|
||||
using (var uow = _uowProvider.GetUnitOfWork())
|
||||
{
|
||||
// populate content and media items
|
||||
var types = new[] { Constants.ObjectTypes.Document, Constants.ObjectTypes.Media };
|
||||
var values = uow.Database.Query<TypedIdDto>("SELECT id, uniqueId, nodeObjectType FROM umbracoNode WHERE nodeObjectType IN @types", new { types });
|
||||
foreach (var value in values)
|
||||
{
|
||||
var umbracoObjectType = UmbracoObjectTypesExtensions.GetUmbracoObjectType(value.NodeObjectType);
|
||||
_id2Key.Add(value.Id, new TypedId<Guid>(value.UniqueId, umbracoObjectType));
|
||||
_key2Id.Add(value.UniqueId, new TypedId<int>(value.Id, umbracoObjectType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Attempt<int> PopulateAndGetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
|
||||
PopulateLocked();
|
||||
|
||||
return _key2Id.TryGetValue(key, out var id) && id.UmbracoObjectType == umbracoObjectType
|
||||
? Attempt.Succeed(id.Id)
|
||||
: Attempt<int>.Fail();
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_locker.IsReadLockHeld)
|
||||
_locker.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
private Attempt<Guid> PopulateAndGetKeyForId(int id, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
|
||||
PopulateLocked();
|
||||
|
||||
return _id2Key.TryGetValue(id, out var key) && key.UmbracoObjectType == umbracoObjectType
|
||||
? Attempt.Succeed(key.Id)
|
||||
: Attempt<Guid>.Fail();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_locker.IsReadLockHeld)
|
||||
_locker.ExitReadLock();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public Attempt<int> GetIdForKey(Guid key, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
TypedId<int> id;
|
||||
bool empty;
|
||||
|
||||
try
|
||||
{
|
||||
_locker.EnterReadLock();
|
||||
if (_key2Id.TryGetValue(key, out id) && id.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(id.Id);
|
||||
if (_key2Id.TryGetValue(key, out var id) && id.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(id.Id);
|
||||
empty = _key2Id.Count == 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -36,20 +163,37 @@ namespace Umbraco.Core.Services
|
||||
_locker.ExitReadLock();
|
||||
}
|
||||
|
||||
int? val;
|
||||
using (var uow = _uowProvider.GetUnitOfWork())
|
||||
#if POPULATE_FROM_DATABASE
|
||||
// if cache is empty and looking for a document or a media,
|
||||
// populate the cache at once and return what we found
|
||||
if (empty && (umbracoObjectType == UmbracoObjectTypes.Document || umbracoObjectType == UmbracoObjectTypes.Media))
|
||||
return PopulateAndGetIdForKey(key, umbracoObjectType);
|
||||
#endif
|
||||
|
||||
// optimize for read speed: reading database outside a lock means that we could read
|
||||
// multiple times, but we don't lock the cache while accessing the database = better
|
||||
|
||||
int? val = null;
|
||||
|
||||
if (_dictionary.TryGetValue(umbracoObjectType, out var mappers))
|
||||
if ((val = mappers.key2id(key)) == default(int)) val = null;
|
||||
|
||||
if (val == null)
|
||||
{
|
||||
//if it's unknown don't include the nodeObjectType in the query
|
||||
if (umbracoObjectType == UmbracoObjectTypes.Unknown)
|
||||
using (var uow = _uowProvider.GetUnitOfWork())
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<int?>("SELECT id FROM umbracoNode WHERE uniqueId=@id", new { id = key});
|
||||
//if it's unknown don't include the nodeObjectType in the query
|
||||
if (umbracoObjectType == UmbracoObjectTypes.Unknown)
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<int?>("SELECT id FROM umbracoNode WHERE uniqueId=@id", new { id = key});
|
||||
}
|
||||
else
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<int?>("SELECT id FROM umbracoNode WHERE uniqueId=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)",
|
||||
new { id = key, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid });
|
||||
}
|
||||
uow.Commit();
|
||||
}
|
||||
else
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<int?>("SELECT id FROM umbracoNode WHERE uniqueId=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)",
|
||||
new { id = key, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid });
|
||||
}
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
if (val == null) return Attempt<int>.Fail();
|
||||
@@ -83,13 +227,23 @@ namespace Umbraco.Core.Services
|
||||
return GetIdForKey(guidUdi.Guid, umbracoType);
|
||||
}
|
||||
|
||||
public Attempt<Udi> GetUdiForId(int id, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
var keyAttempt = GetKeyForId(id, umbracoObjectType);
|
||||
return keyAttempt
|
||||
? Attempt.Succeed<Udi>(new GuidUdi(Constants.UdiEntityType.FromUmbracoObjectType(umbracoObjectType), keyAttempt.Result))
|
||||
: Attempt<Udi>.Fail();
|
||||
}
|
||||
|
||||
public Attempt<Guid> GetKeyForId(int id, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
TypedId<Guid> key;
|
||||
bool empty;
|
||||
|
||||
try
|
||||
{
|
||||
_locker.EnterReadLock();
|
||||
if (_id2Key.TryGetValue(id, out key) && key.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(key.Id);
|
||||
if (_id2Key.TryGetValue(id, out var key) && key.UmbracoObjectType == umbracoObjectType) return Attempt.Succeed(key.Id);
|
||||
empty = _id2Key.Count == 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -97,20 +251,37 @@ namespace Umbraco.Core.Services
|
||||
_locker.ExitReadLock();
|
||||
}
|
||||
|
||||
Guid? val;
|
||||
using (var uow = _uowProvider.GetUnitOfWork())
|
||||
#if POPULATE_FROM_DATABASE
|
||||
// if cache is empty and looking for a document or a media,
|
||||
// populate the cache at once and return what we found
|
||||
if (empty && (umbracoObjectType == UmbracoObjectTypes.Document || umbracoObjectType == UmbracoObjectTypes.Media))
|
||||
return PopulateAndGetKeyForId(id, umbracoObjectType);
|
||||
#endif
|
||||
|
||||
// optimize for read speed: reading database outside a lock means that we could read
|
||||
// multiple times, but we don't lock the cache while accessing the database = better
|
||||
|
||||
Guid? val = null;
|
||||
|
||||
if (_dictionary.TryGetValue(umbracoObjectType, out var mappers))
|
||||
if ((val = mappers.id2key(id)) == default(Guid)) val = null;
|
||||
|
||||
if (val == null)
|
||||
{
|
||||
//if it's unknown don't include the nodeObjectType in the query
|
||||
if (umbracoObjectType == UmbracoObjectTypes.Unknown)
|
||||
using (var uow = _uowProvider.GetUnitOfWork())
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<Guid?>("SELECT uniqueId FROM umbracoNode WHERE id=@id", new { id });
|
||||
//if it's unknown don't include the nodeObjectType in the query
|
||||
if (umbracoObjectType == UmbracoObjectTypes.Unknown)
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<Guid?>("SELECT uniqueId FROM umbracoNode WHERE id=@id", new { id });
|
||||
}
|
||||
else
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<Guid?>("SELECT uniqueId FROM umbracoNode WHERE id=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)",
|
||||
new { id, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid });
|
||||
}
|
||||
uow.Commit();
|
||||
}
|
||||
else
|
||||
{
|
||||
val = uow.Database.ExecuteScalar<Guid?>("SELECT uniqueId FROM umbracoNode WHERE id=@id AND (nodeObjectType=@type OR nodeObjectType=@reservation)",
|
||||
new { id, type = GetNodeObjectTypeGuid(umbracoObjectType), reservation = Constants.ObjectTypes.IdReservationGuid });
|
||||
}
|
||||
uow.Commit();
|
||||
}
|
||||
|
||||
if (val == null) return Attempt<Guid>.Fail();
|
||||
@@ -142,6 +313,8 @@ namespace Umbraco.Core.Services
|
||||
return guid;
|
||||
}
|
||||
|
||||
// invoked on UnpublishedPageCacheRefresher.RefreshAll
|
||||
// anything else will use the id-specific overloads
|
||||
public void ClearCache()
|
||||
{
|
||||
try
|
||||
@@ -162,8 +335,7 @@ namespace Umbraco.Core.Services
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
TypedId<Guid> key;
|
||||
if (_id2Key.TryGetValue(id, out key) == false) return;
|
||||
if (_id2Key.TryGetValue(id, out var key) == false) return;
|
||||
_id2Key.Remove(id);
|
||||
_key2Id.Remove(key.Id);
|
||||
}
|
||||
@@ -179,8 +351,7 @@ namespace Umbraco.Core.Services
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
TypedId<int> id;
|
||||
if (_key2Id.TryGetValue(key, out id) == false) return;
|
||||
if (_key2Id.TryGetValue(key, out var id) == false) return;
|
||||
_id2Key.Remove(id.Id);
|
||||
_key2Id.Remove(key);
|
||||
}
|
||||
@@ -191,26 +362,28 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable ClassNeverInstantiated.Local
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Local
|
||||
private class TypedIdDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public Guid UniqueId { get; set; }
|
||||
public Guid NodeObjectType { get; set; }
|
||||
}
|
||||
// ReSharper restore ClassNeverInstantiated.Local
|
||||
// ReSharper restore UnusedAutoPropertyAccessor.Local
|
||||
|
||||
private struct TypedId<T>
|
||||
{
|
||||
private readonly T _id;
|
||||
private readonly UmbracoObjectTypes _umbracoObjectType;
|
||||
|
||||
public T Id
|
||||
{
|
||||
get { return _id; }
|
||||
}
|
||||
|
||||
public UmbracoObjectTypes UmbracoObjectType
|
||||
{
|
||||
get { return _umbracoObjectType; }
|
||||
}
|
||||
|
||||
public TypedId(T id, UmbracoObjectTypes umbracoObjectType)
|
||||
{
|
||||
_umbracoObjectType = umbracoObjectType;
|
||||
_id = id;
|
||||
UmbracoObjectType = umbracoObjectType;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public UmbracoObjectTypes UmbracoObjectType { get; }
|
||||
|
||||
public T Id { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +115,9 @@
|
||||
<Reference Include="System.Runtime.Caching" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Transactions" />
|
||||
<Reference Include="System.ValueTuple, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Web.ApplicationServices" />
|
||||
<Reference Include="System.Web.Extensions" />
|
||||
|
||||
@@ -18,4 +18,5 @@
|
||||
<package id="Owin" version="1.0" targetFramework="net45" />
|
||||
<package id="semver" version="1.1.2" targetFramework="net45" />
|
||||
<package id="SqlServerCE" version="4.0.0.1" targetFramework="net45" />
|
||||
<package id="System.ValueTuple" version="4.4.0" targetFramework="net45" />
|
||||
</packages>
|
||||
@@ -139,8 +139,8 @@
|
||||
<Reference Include="System.Threading.Thread, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ValueTuple, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
|
||||
<Reference Include="System.ValueTuple, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
|
||||
@@ -62,6 +62,10 @@
|
||||
<assemblyIdentity name="System.Xml.XPath.XDocument" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.ValueTuple" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -51,7 +51,7 @@
|
||||
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Threading.Tasks.Parallel" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Threading.Thread" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.ValueTuple" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.ValueTuple" version="4.4.0" targetFramework="net46" />
|
||||
<package id="System.Xml.ReaderWriter" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net46" />
|
||||
<package id="System.Xml.XmlDocument" version="4.3.0" targetFramework="net46" />
|
||||
|
||||
@@ -155,7 +155,8 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
foreach (var payload in payloads)
|
||||
{
|
||||
ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id);
|
||||
if (payload.Operation == OperationType.Deleted)
|
||||
ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id);
|
||||
|
||||
var mediaCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache<IMedia>();
|
||||
|
||||
@@ -190,4 +191,4 @@ namespace Umbraco.Web.Cache
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,6 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
public override void Refresh(int id)
|
||||
{
|
||||
ApplicationContext.Current.Services.IdkMap.ClearCache(id);
|
||||
ClearRepositoryCacheItemById(id);
|
||||
ClearAllIsolatedCacheByEntityType<PublicAccessEntry>();
|
||||
content.Instance.UpdateSortOrder(id);
|
||||
@@ -104,7 +103,6 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
public override void Refresh(IContent instance)
|
||||
{
|
||||
ApplicationContext.Current.Services.IdkMap.ClearCache(instance.Id);
|
||||
ClearRepositoryCacheItemById(instance.Id);
|
||||
ClearAllIsolatedCacheByEntityType<PublicAccessEntry>();
|
||||
content.Instance.UpdateSortOrder(instance);
|
||||
@@ -150,4 +148,4 @@ namespace Umbraco.Web.Cache
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,7 @@ namespace Umbraco.Web.PublishedCache
|
||||
/// <remarks>Considers published or unpublished content depending on context.</remarks>
|
||||
public IPublishedContent GetById(Guid contentId)
|
||||
{
|
||||
var intId = UmbracoContext.Application.Services.EntityService.GetIdForKey(contentId, UmbracoObjectTypes.Document);
|
||||
return GetById(intId.Success ? intId.Result : -1);
|
||||
return GetById(UmbracoContext.InPreviewMode, contentId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -59,6 +58,15 @@ namespace Umbraco.Web.PublishedCache
|
||||
/// <returns>The content, or null.</returns>
|
||||
public abstract IPublishedContent GetById(bool preview, int contentId);
|
||||
|
||||
// same with Guid
|
||||
// cannot make this public nor abstract without breaking backward compatibility
|
||||
public virtual IPublishedContent GetById(bool preview, Guid contentKey)
|
||||
{
|
||||
// original implementation - override in concrete classes
|
||||
var intId = UmbracoContext.Application.Services.EntityService.GetIdForKey(contentKey, UmbracoObjectTypes.Document);
|
||||
return GetById(intId.Success ? intId.Result : -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets content at root.
|
||||
/// </summary>
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.PublishedCache.XmlPublishedCache;
|
||||
|
||||
namespace Umbraco.Web.PublishedCache
|
||||
{
|
||||
@@ -20,6 +21,14 @@ namespace Umbraco.Web.PublishedCache
|
||||
: base(umbracoContext, cache)
|
||||
{ }
|
||||
|
||||
public override IPublishedContent GetById(bool preview, Guid contentKey)
|
||||
{
|
||||
if (InnerCache is PublishedContentCache cc)
|
||||
return cc.GetById(UmbracoContext, preview, contentKey);
|
||||
|
||||
return base.GetById(preview, contentKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets content identified by a route.
|
||||
/// </summary>
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.PublishedCache.XmlPublishedCache;
|
||||
|
||||
namespace Umbraco.Web.PublishedCache
|
||||
{
|
||||
@@ -19,5 +20,13 @@ namespace Umbraco.Web.PublishedCache
|
||||
internal ContextualPublishedMediaCache(IPublishedMediaCache cache, UmbracoContext umbracoContext)
|
||||
: base(umbracoContext, cache)
|
||||
{ }
|
||||
|
||||
public override IPublishedContent GetById(bool preview, Guid contentKey)
|
||||
{
|
||||
if (InnerCache is PublishedMediaCache cc)
|
||||
return cc.GetById(UmbracoContext, preview, contentKey);
|
||||
|
||||
return base.GetById(preview, contentKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,11 +346,89 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
|
||||
#region Getters
|
||||
|
||||
private readonly object _idkMapLocker = new object();
|
||||
private IdkMap _idkMap;
|
||||
|
||||
// populate the idkmap by indexing the content cache
|
||||
// assuming that the content cache cannot be corrupted
|
||||
private void EnsureIdkMap(UmbracoContext umbracoContext)
|
||||
{
|
||||
lock (_idkMapLocker)
|
||||
{
|
||||
if (_idkMap != null) return;
|
||||
|
||||
_idkMap = ApplicationContext.Current.Services.IdkMap; // fixme inject
|
||||
|
||||
// give the map a fast mapper
|
||||
_idkMap.SetMapper(UmbracoObjectTypes.Document, GetKeyForId, GetIdForKey);
|
||||
|
||||
// populate the map with what we know, so far
|
||||
var xml = GetXml(umbracoContext, false);
|
||||
var nav = xml.CreateNavigator();
|
||||
var iter = nav.SelectDescendants(XPathNodeType.Element, true);
|
||||
_idkMap.Populate(Enumerate(iter), UmbracoObjectTypes.Document);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<(int id, Guid key)> Enumerate(XPathNodeIterator iter)
|
||||
{
|
||||
while (iter.MoveNext())
|
||||
{
|
||||
string idString = null;
|
||||
string keyString = null;
|
||||
|
||||
if (iter.Current.MoveToFirstAttribute())
|
||||
{
|
||||
do
|
||||
{
|
||||
switch (iter.Current.Name)
|
||||
{
|
||||
case "id":
|
||||
idString = iter.Current.Value;
|
||||
break;
|
||||
case "key":
|
||||
keyString = iter.Current.Value;
|
||||
break;
|
||||
}
|
||||
} while ((idString == null || keyString == null) && iter.Current.MoveToNextAttribute());
|
||||
|
||||
iter.Current.MoveToParent();
|
||||
}
|
||||
|
||||
if (idString == null || keyString == null) continue;
|
||||
|
||||
var id = int.Parse(idString);
|
||||
var key = Guid.Parse(keyString);
|
||||
yield return (id, key);
|
||||
}
|
||||
}
|
||||
|
||||
private Guid GetKeyForId(int id)
|
||||
{
|
||||
var xml = GetXml(UmbracoContext.Current, false);
|
||||
var elt = xml.GetElementById(id.ToString(CultureInfo.InvariantCulture));
|
||||
return elt == null ? default (Guid) : Guid.Parse(elt.GetAttribute("key"));
|
||||
}
|
||||
|
||||
private int GetIdForKey(Guid key)
|
||||
{
|
||||
var xml = GetXml(UmbracoContext.Current, false);
|
||||
var elt = xml.SelectSingleNode("//* [@key=$guid]", new XPathVariable("guid", key.ToString())) as XmlElement;
|
||||
return elt == null ? default (int) : int.Parse(elt.GetAttribute("id"));
|
||||
}
|
||||
|
||||
public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, int nodeId)
|
||||
{
|
||||
return ConvertToDocument(GetXml(umbracoContext, preview).GetElementById(nodeId.ToString(CultureInfo.InvariantCulture)), preview);
|
||||
}
|
||||
|
||||
public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, Guid nodeKey)
|
||||
{
|
||||
EnsureIdkMap(umbracoContext);
|
||||
var mapAttempt = _idkMap.GetIdForKey(nodeKey, UmbracoObjectTypes.Document);
|
||||
return mapAttempt ? GetById(umbracoContext, preview, mapAttempt.Result) : null;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<IPublishedContent> GetAtRoot(UmbracoContext umbracoContext, bool preview)
|
||||
{
|
||||
return ConvertToDocuments(GetXml(umbracoContext, preview).SelectNodes(XPathStrings.RootDocuments), preview);
|
||||
@@ -490,4 +568,4 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
return GetUmbracoMedia(nodeId);
|
||||
}
|
||||
|
||||
public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, Guid nodeKey)
|
||||
{
|
||||
// TODO optimize with Examine?
|
||||
var mapAttempt = ApplicationContext.Current.Services.IdkMap.GetIdForKey(nodeKey, UmbracoObjectTypes.Media);
|
||||
return mapAttempt ? GetById(umbracoContext, preview, mapAttempt.Result) : null;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<IPublishedContent> GetAtRoot(UmbracoContext umbracoContext, bool preview)
|
||||
{
|
||||
var searchProvider = GetSearchProviderSafe();
|
||||
|
||||
@@ -13,6 +13,7 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Dynamics;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Xml;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
@@ -232,13 +233,9 @@ namespace Umbraco.Web
|
||||
return doc;
|
||||
}
|
||||
|
||||
private IPublishedContent TypedDocumentById(Guid id, ContextualPublishedCache cache)
|
||||
private IPublishedContent TypedDocumentById(Guid key, ContextualPublishedCache cache)
|
||||
{
|
||||
// todo: in v8, implement in a more efficient way
|
||||
var legacyXml = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema;
|
||||
var xpath = legacyXml ? "//node [@key=$guid]" : "//* [@key=$guid]";
|
||||
var doc = cache.GetSingleByXPath(xpath, new XPathVariable("guid", id.ToString()));
|
||||
return doc;
|
||||
return cache.GetById(key);
|
||||
}
|
||||
|
||||
private IPublishedContent TypedDocumentByXPath(string xpath, XPathVariable[] vars, ContextualPublishedContentCache cache)
|
||||
@@ -261,7 +258,6 @@ namespace Umbraco.Web
|
||||
|
||||
private IEnumerable<IPublishedContent> TypedDocumentsByIds(ContextualPublishedCache cache, IEnumerable<Guid> ids)
|
||||
{
|
||||
// todo: in v8, implement in a more efficient way
|
||||
return ids.Select(eachId => TypedDocumentById(eachId, cache)).WhereNotNull();
|
||||
}
|
||||
|
||||
@@ -548,4 +544,4 @@ namespace Umbraco.Web
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +202,9 @@
|
||||
<Reference Include="System.Threading.Tasks.Dataflow, Version=4.6.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.Threading.Tasks.Dataflow.4.7.0\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ValueTuple, Version=4.0.2.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Web.Abstractions">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
|
||||
@@ -28,4 +28,5 @@
|
||||
<package id="Owin" version="1.0" targetFramework="net45" />
|
||||
<package id="semver" version="1.1.2" targetFramework="net45" />
|
||||
<package id="System.Threading.Tasks.Dataflow" version="4.7.0" targetFramework="net45" />
|
||||
<package id="System.ValueTuple" version="4.4.0" targetFramework="net45" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user