Merge pull request #2505 from umbraco/temp-u4-10756

U4-10756 - id/guid cache
This commit is contained in:
Shannon Deminick
2018-03-21 11:42:04 +11:00
committed by GitHub
16 changed files with 356 additions and 65 deletions
+219 -46
View File
@@ -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; }
}
}
}
}
+3
View File
@@ -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" />
+1
View File
@@ -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" />
+4
View File
@@ -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>
+1 -1
View File
@@ -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" />
+3 -2
View File
@@ -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();
+4 -8
View File
@@ -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
}
}
}
+3
View File
@@ -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>
+1
View File
@@ -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>