From 573fd2c902ecfb7b070aedcb0089bed412c94673 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Tue, 20 Dec 2011 18:16:39 -0800 Subject: [PATCH] Preliminary performance optimizations --HG-- branch : 1.x --- .../Services/MainMenuNavigationProvider.cs | 2 +- .../Services/RoutableHomePageProvider.cs | 2 +- .../Core/Settings/Services/SiteService.cs | 2 +- .../Security/AccessFrontEndFilter.cs | 2 +- .../Services/WidgetsService.cs | 2 + .../ContentManagement/ContentExtensions.cs | 4 + .../DefaultContentManager.cs | 88 +++++++++++++------ .../DefaultContentManagerSession.cs | 10 +++ .../ContentManagement/DefaultContentQuery.cs | 46 ++++++++++ .../ContentManagement/IContentManager.cs | 8 ++ .../IContentManagerSession.cs | 1 + .../ContentManagement/IContentQuery.cs | 3 + src/Orchard/Tasks/BackgroundService.cs | 1 + 13 files changed, 141 insertions(+), 30 deletions(-) diff --git a/src/Orchard.Web/Core/Navigation/Services/MainMenuNavigationProvider.cs b/src/Orchard.Web/Core/Navigation/Services/MainMenuNavigationProvider.cs index b5dd18bd4..32a2cdbfb 100644 --- a/src/Orchard.Web/Core/Navigation/Services/MainMenuNavigationProvider.cs +++ b/src/Orchard.Web/Core/Navigation/Services/MainMenuNavigationProvider.cs @@ -17,7 +17,7 @@ namespace Orchard.Core.Navigation.Services { public string MenuName { get { return "main"; } } public void GetNavigation(NavigationBuilder builder) { - var menuParts = _contentManager.Query().Where(x => x.OnMainMenu).List(); + var menuParts = _contentManager.Query().Where(x => x.OnMainMenu).WithQueryHints(new QueryHints().ExpandRecords()).List(); foreach (var menuPart in menuParts) { if (menuPart != null) { var part = menuPart; diff --git a/src/Orchard.Web/Core/Routable/Services/RoutableHomePageProvider.cs b/src/Orchard.Web/Core/Routable/Services/RoutableHomePageProvider.cs index 24a00b28a..9202b24dc 100644 --- a/src/Orchard.Web/Core/Routable/Services/RoutableHomePageProvider.cs +++ b/src/Orchard.Web/Core/Routable/Services/RoutableHomePageProvider.cs @@ -42,7 +42,7 @@ namespace Orchard.Core.Routable.Services { } public ActionResult GetHomePage(int id) { - var contentItem = _contentManager.Get(id, VersionOptions.Published); + var contentItem = _contentManager.Get(id, VersionOptions.Published, new QueryHints().ExpandRecords()); if (contentItem == null || !contentItem.Is()) return new HttpNotFoundResult(); diff --git a/src/Orchard.Web/Core/Settings/Services/SiteService.cs b/src/Orchard.Web/Core/Settings/Services/SiteService.cs index e9a9f140f..5fc76e488 100644 --- a/src/Orchard.Web/Core/Settings/Services/SiteService.cs +++ b/src/Orchard.Web/Core/Settings/Services/SiteService.cs @@ -42,7 +42,7 @@ namespace Orchard.Core.Settings.Services { return site.Id; }); - return _contentManager.Get(siteId); + return _contentManager.Get(siteId, VersionOptions.Published, new QueryHints().ExpandRecords()); } } } \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Users/Security/AccessFrontEndFilter.cs b/src/Orchard.Web/Modules/Orchard.Users/Security/AccessFrontEndFilter.cs index 38416591a..6fbf9ef1d 100644 --- a/src/Orchard.Web/Modules/Orchard.Users/Security/AccessFrontEndFilter.cs +++ b/src/Orchard.Web/Modules/Orchard.Users/Security/AccessFrontEndFilter.cs @@ -24,7 +24,7 @@ namespace Orchard.Users.Security { || filterContext.ActionDescriptor.ActionName == "RequestLostPassword") && filterContext.ActionDescriptor.ControllerDescriptor.ControllerName == "Account"; - if (!AdminFilter.IsApplied(filterContext.RequestContext) && !isAuthPage && !_authorizer.Authorize(StandardPermissions.AccessFrontEnd, T("Can't access this website"))) { + if (!AdminFilter.IsApplied(filterContext.RequestContext) && !isAuthPage && !_authorizer.Authorize(StandardPermissions.AccessFrontEnd)) { filterContext.Result = new HttpUnauthorizedResult(); } } diff --git a/src/Orchard.Web/Modules/Orchard.Widgets/Services/WidgetsService.cs b/src/Orchard.Web/Modules/Orchard.Widgets/Services/WidgetsService.cs index 20ceaea3e..e6a6cb665 100644 --- a/src/Orchard.Web/Modules/Orchard.Widgets/Services/WidgetsService.cs +++ b/src/Orchard.Web/Modules/Orchard.Widgets/Services/WidgetsService.cs @@ -8,6 +8,7 @@ using Orchard.Environment.Extensions; using Orchard.Environment.Extensions.Models; using Orchard.Environment.Features; using Orchard.Widgets.Models; +using Orchard.Core.Common.Models; namespace Orchard.Widgets.Services { @@ -49,6 +50,7 @@ namespace Orchard.Widgets.Services { private IEnumerable GetAllWidgets() { return _contentManager .Query() + .WithQueryHints(new QueryHints().ExpandRecords()) .List(); } diff --git a/src/Orchard/ContentManagement/ContentExtensions.cs b/src/Orchard/ContentManagement/ContentExtensions.cs index 5d8532a43..179b97bde 100644 --- a/src/Orchard/ContentManagement/ContentExtensions.cs +++ b/src/Orchard/ContentManagement/ContentExtensions.cs @@ -156,6 +156,10 @@ namespace Orchard.ContentManagement { var contentItem = manager.Get(id, options); return contentItem == null ? null : contentItem.Get(); } + public static T Get(this IContentManager manager, int id, VersionOptions options, QueryHints hints) where T : class, IContent { + var contentItem = manager.Get(id, options, hints); + return contentItem == null ? null : contentItem.Get(); + } public static T GetLatest(this IContentManager manager, int id) where T : class, IContent { return Get(manager, id, VersionOptions.Latest); } diff --git a/src/Orchard/ContentManagement/DefaultContentManager.cs b/src/Orchard/ContentManagement/DefaultContentManager.cs index d95441c8e..2e65b4455 100644 --- a/src/Orchard/ContentManagement/DefaultContentManager.cs +++ b/src/Orchard/ContentManagement/DefaultContentManager.cs @@ -107,6 +107,10 @@ namespace Orchard.ContentManagement { } public virtual ContentItem Get(int id, VersionOptions options) { + return Get(id, options, QueryHints.Empty); + } + + public virtual ContentItem Get(int id, VersionOptions options, QueryHints hints) { var session = _contentManagerSession(); ContentItem contentItem; @@ -119,13 +123,43 @@ namespace Orchard.ContentManagement { return contentItem; } - // locate explicit version record versionRecord = _contentItemVersionRepository.Get(options.VersionRecordId); } + else if (session.RecallContentRecordId(id, out contentItem)) { + // try to reload a previously loaded published content item + + if (options.IsPublished) { + return contentItem; + } + + versionRecord = contentItem.VersionRecord; + } else { - var record = _contentItemRepository.Get(id); - if (record != null) - versionRecord = GetVersionRecord(options, record); + // do a query to load the records in case Get is called directly + var contentItemVersionRecords = GetManyImplementation(hints, + (contentItemCriteria, contentItemVersionCriteria) => { + contentItemCriteria.Add(Restrictions.Eq("Id", id)); + if (options.IsPublished) { + contentItemVersionCriteria.Add(Restrictions.Eq("Published", true)); + } + else if (options.IsLatest) { + contentItemVersionCriteria.Add(Restrictions.Eq("Latest", true)); + } + else if (options.IsDraft && !options.IsDraftRequired) { + contentItemVersionCriteria.Add( + Restrictions.And(Restrictions.Eq("Published", false), + Restrictions.Eq("Latest", true))); + } + else if (options.IsDraft || options.IsDraftRequired) { + contentItemVersionCriteria.Add(Restrictions.Eq("Latest", true)); + } + + contentItemVersionCriteria.SetFetchMode("ContentItemRecord", FetchMode.Eager); + contentItemVersionCriteria.SetFetchMode("ContentItemRecord.ContentType", FetchMode.Eager); + contentItemVersionCriteria.SetMaxResults(1); + }); + + versionRecord = contentItemVersionRecords.FirstOrDefault(); } // no record means content item doesn't exist @@ -147,7 +181,7 @@ namespace Orchard.ContentManagement { // store in session prior to loading to avoid some problems with simple circular dependencies session.Store(contentItem); - + // create a context with a new instance to load var context = new LoadContentContext(contentItem); @@ -157,10 +191,10 @@ namespace Orchard.ContentManagement { // when draft is required and latest is published a new version is appended if (options.IsDraftRequired && versionRecord.Published) { - return BuildNewVersion(context.ContentItem); + contentItem = BuildNewVersion(context.ContentItem); } - return context.ContentItem; + return contentItem; } private ContentItemVersionRecord GetVersionRecord(VersionOptions options, ContentItemRecord itemRecord) { @@ -195,7 +229,7 @@ namespace Orchard.ContentManagement { return _contentItemVersionRepository .Fetch(x => x.ContentItemRecord.Id == id) .OrderBy(x => x.Number) - .Select(x => Get(x.ContentItemRecord.Id, VersionOptions.VersionRecord(x.Id))); + .Select(x => Get(x.Id, VersionOptions.VersionRecord(x.Id))); } public IEnumerable GetMany(IEnumerable ids, VersionOptions options, QueryHints hints) where T : class, IContent { @@ -248,27 +282,29 @@ namespace Orchard.ContentManagement { var contentItemMetadata = session.SessionFactory.GetClassMetadata(typeof (ContentItemRecord)); var contentItemVersionMetadata = session.SessionFactory.GetClassMetadata(typeof (ContentItemVersionRecord)); - // break apart and group hints by their first segment - var hintDictionary = hints.Records - .Select(hint=>new {Hint=hint, Segments=hint.Split('.')}) - .GroupBy(item=>item.Segments.FirstOrDefault()) - .ToDictionary(grouping=>grouping.Key, StringComparer.InvariantCultureIgnoreCase); + if (hints != QueryHints.Empty) { + // break apart and group hints by their first segment + var hintDictionary = hints.Records + .Select(hint => new { Hint = hint, Segments = hint.Split('.') }) + .GroupBy(item => item.Segments.FirstOrDefault()) + .ToDictionary(grouping => grouping.Key, StringComparer.InvariantCultureIgnoreCase); - // locate hints that match properties in the ContentItemVersionRecord - foreach (var hit in contentItemVersionMetadata.PropertyNames.Where(hintDictionary.ContainsKey).SelectMany(key=>hintDictionary[key])) { - contentItemVersionCriteria.SetFetchMode(hit.Hint, FetchMode.Eager); - hit.Segments.Take(hit.Segments.Count() - 1).Aggregate(contentItemVersionCriteria, ExtendCriteria); + // locate hints that match properties in the ContentItemVersionRecord + foreach (var hit in contentItemVersionMetadata.PropertyNames.Where(hintDictionary.ContainsKey).SelectMany(key => hintDictionary[key])) { + contentItemVersionCriteria.SetFetchMode(hit.Hint, FetchMode.Eager); + hit.Segments.Take(hit.Segments.Count() - 1).Aggregate(contentItemVersionCriteria, ExtendCriteria); + } + + // locate hints that match properties in the ContentItemRecord + foreach (var hit in contentItemMetadata.PropertyNames.Where(hintDictionary.ContainsKey).SelectMany(key => hintDictionary[key])) { + contentItemVersionCriteria.SetFetchMode("ContentItemRecord." + hit.Hint, FetchMode.Eager); + hit.Segments.Take(hit.Segments.Count() - 1).Aggregate(contentItemCriteria, ExtendCriteria); + } + + if (hintDictionary.SelectMany(x => x.Value).Any(x => x.Segments.Count() > 1)) + contentItemVersionCriteria.SetResultTransformer(new DistinctRootEntityResultTransformer()); } - // locate hints that match properties in the ContentItemRecord - foreach (var hit in contentItemMetadata.PropertyNames.Where(hintDictionary.ContainsKey).SelectMany(key=>hintDictionary[key])) { - contentItemVersionCriteria.SetFetchMode("ContentItemRecord." + hit.Hint, FetchMode.Eager); - hit.Segments.Take(hit.Segments.Count() - 1).Aggregate(contentItemCriteria, ExtendCriteria); - } - - if (hintDictionary.SelectMany(x=>x.Value).Any(x=>x.Segments.Count() > 1)) - contentItemVersionCriteria.SetResultTransformer(new DistinctRootEntityResultTransformer()); - return contentItemVersionCriteria.List(); } diff --git a/src/Orchard/ContentManagement/DefaultContentManagerSession.cs b/src/Orchard/ContentManagement/DefaultContentManagerSession.cs index ad6b78735..ebf8ddfea 100644 --- a/src/Orchard/ContentManagement/DefaultContentManagerSession.cs +++ b/src/Orchard/ContentManagement/DefaultContentManagerSession.cs @@ -3,13 +3,23 @@ namespace Orchard.ContentManagement { public class DefaultContentManagerSession : IContentManagerSession { private readonly IDictionary _itemByVersionRecordId = new Dictionary(); + private readonly IDictionary _publishedItemsByContentRecordId = new Dictionary(); public void Store(ContentItem item) { _itemByVersionRecordId.Add(item.VersionRecord.Id, item); + + // is it the Published version ? + if (item.VersionRecord.Latest && item.VersionRecord.Published) { + _publishedItemsByContentRecordId.Add(item.Id, item); + } } public bool RecallVersionRecordId(int id, out ContentItem item) { return _itemByVersionRecordId.TryGetValue(id, out item); } + + public bool RecallContentRecordId(int id, out ContentItem item) { + return _publishedItemsByContentRecordId.TryGetValue(id, out item); + } } } diff --git a/src/Orchard/ContentManagement/DefaultContentQuery.cs b/src/Orchard/ContentManagement/DefaultContentQuery.cs index 94411e2bb..8c773fa36 100644 --- a/src/Orchard/ContentManagement/DefaultContentQuery.cs +++ b/src/Orchard/ContentManagement/DefaultContentQuery.cs @@ -9,6 +9,8 @@ using NHibernate.Linq; using Orchard.ContentManagement.Records; using Orchard.Data; using Orchard.Utility.Extensions; +using NHibernate.Transform; +using NHibernate.SqlCommand; namespace Orchard.ContentManagement { public class DefaultContentQuery : IContentQuery { @@ -129,6 +131,9 @@ namespace Orchard.ContentManagement { criteria.ApplyVersionOptionsRestrictions(_versionOptions); + criteria.SetFetchMode("ContentItemRecord", FetchMode.Eager); + criteria.SetFetchMode("ContentItemRecord.ContentType", FetchMode.Eager); + // TODO: put 'removed false' filter in place if (skip != 0) { criteria = criteria.SetFirstResult(skip); @@ -240,6 +245,47 @@ namespace Orchard.ContentManagement { _query.OrderByDescending(keySelector); return this; } + + + public IContentQuery WithQueryHints(QueryHints hints) { + if (hints == QueryHints.Empty) { + return this; + } + + var contentItemVersionCriteria = _query.BindItemVersionCriteria(); + var contentItemCriteria = _query.BindItemCriteria(); + + var contentItemMetadata = _query._session.SessionFactory.GetClassMetadata(typeof(ContentItemRecord)); + var contentItemVersionMetadata = _query._session.SessionFactory.GetClassMetadata(typeof(ContentItemVersionRecord)); + + // break apart and group hints by their first segment + var hintDictionary = hints.Records + .Select(hint => new { Hint = hint, Segments = hint.Split('.') }) + .GroupBy(item => item.Segments.FirstOrDefault()) + .ToDictionary(grouping => grouping.Key, StringComparer.InvariantCultureIgnoreCase); + + // locate hints that match properties in the ContentItemVersionRecord + foreach (var hit in contentItemVersionMetadata.PropertyNames.Where(hintDictionary.ContainsKey).SelectMany(key => hintDictionary[key])) { + contentItemVersionCriteria.SetFetchMode(hit.Hint, FetchMode.Eager); + hit.Segments.Take(hit.Segments.Count() - 1).Aggregate(contentItemVersionCriteria, ExtendCriteria); + } + + // locate hints that match properties in the ContentItemRecord + foreach (var hit in contentItemMetadata.PropertyNames.Where(hintDictionary.ContainsKey).SelectMany(key => hintDictionary[key])) { + contentItemVersionCriteria.SetFetchMode("ContentItemRecord." + hit.Hint, FetchMode.Eager); + hit.Segments.Take(hit.Segments.Count() - 1).Aggregate(contentItemCriteria, ExtendCriteria); + } + + if (hintDictionary.SelectMany(x => x.Value).Any(x => x.Segments.Count() > 1)) + contentItemVersionCriteria.SetResultTransformer(new DistinctRootEntityResultTransformer()); + + return this; + } + + private static ICriteria ExtendCriteria(ICriteria criteria, string segment) { + return criteria.GetCriteriaByPath(segment) ?? criteria.CreateCriteria(segment, JoinType.LeftOuterJoin); + } + } } diff --git a/src/Orchard/ContentManagement/IContentManager.cs b/src/Orchard/ContentManagement/IContentManager.cs index 297f32d3c..921ec77d9 100644 --- a/src/Orchard/ContentManagement/IContentManager.cs +++ b/src/Orchard/ContentManagement/IContentManager.cs @@ -48,6 +48,14 @@ namespace Orchard.ContentManagement { /// The version option ContentItem Get(int id, VersionOptions options); + /// + /// Gets the content item with the specified id, version and query hints + /// + /// Numeric id of the content item + /// The version option + /// The query hints + ContentItem Get(int id, VersionOptions options, QueryHints hints); + /// /// Gets all versions of the content item specified with its id /// diff --git a/src/Orchard/ContentManagement/IContentManagerSession.cs b/src/Orchard/ContentManagement/IContentManagerSession.cs index 5664a62d9..3c2dd0a23 100644 --- a/src/Orchard/ContentManagement/IContentManagerSession.cs +++ b/src/Orchard/ContentManagement/IContentManagerSession.cs @@ -2,5 +2,6 @@ public interface IContentManagerSession : IDependency { void Store(ContentItem item); bool RecallVersionRecordId(int id, out ContentItem item); + bool RecallContentRecordId(int id, out ContentItem item); } } diff --git a/src/Orchard/ContentManagement/IContentQuery.cs b/src/Orchard/ContentManagement/IContentQuery.cs index b436f1d4d..ff37958a6 100644 --- a/src/Orchard/ContentManagement/IContentQuery.cs +++ b/src/Orchard/ContentManagement/IContentQuery.cs @@ -31,6 +31,9 @@ namespace Orchard.ContentManagement { IContentQuery Where(Expression> predicate); IContentQuery OrderBy(Expression> keySelector); IContentQuery OrderByDescending(Expression> keySelector); + + IContentQuery WithQueryHints(QueryHints hints); + } } diff --git a/src/Orchard/Tasks/BackgroundService.cs b/src/Orchard/Tasks/BackgroundService.cs index c3e08beaa..aa0dedfb2 100644 --- a/src/Orchard/Tasks/BackgroundService.cs +++ b/src/Orchard/Tasks/BackgroundService.cs @@ -25,6 +25,7 @@ namespace Orchard.Tasks { public ILogger Logger { get; set; } public void Sweep() { + return; foreach(var task in _tasks.OfType()) { using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew)) { try {