From 60924d93fb3bfa34b33c7995b830e6209165edd7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 15 Dec 2017 17:24:54 +1100 Subject: [PATCH 1/2] U4-9291 TypedSearch should have an overload specifying pager values, page size, page index, and with an out param to return the total number of results --- .../ITypedPublishedContentQuery.cs | 23 +++ src/Umbraco.Web/PublishedContentQuery.cs | 158 ++++++++++++++++-- src/Umbraco.Web/UmbracoHelper.cs | 73 +++++--- 3 files changed, 216 insertions(+), 38 deletions(-) diff --git a/src/Umbraco.Web/ITypedPublishedContentQuery.cs b/src/Umbraco.Web/ITypedPublishedContentQuery.cs index 893c036958..f075727b5c 100644 --- a/src/Umbraco.Web/ITypedPublishedContentQuery.cs +++ b/src/Umbraco.Web/ITypedPublishedContentQuery.cs @@ -50,6 +50,18 @@ namespace Umbraco.Web /// IEnumerable TypedSearch(string term, bool useWildCards = true, string searchProvider = null); + /// + /// Searches content + /// + /// + /// + /// + /// + /// + /// + /// + IEnumerable TypedSearch(int skip, int take, out int totalRecords, string term, bool useWildCards = true, string searchProvider = null); + /// /// Searhes content /// @@ -57,5 +69,16 @@ namespace Umbraco.Web /// /// IEnumerable TypedSearch(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null); + + /// + /// Searhes content + /// + /// + /// + /// + /// + /// + /// + IEnumerable TypedSearch(int skip, int take, out int totalrecords, Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null); } } \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index f0f2461ad8..cea5ee7c93 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -1,7 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Xml.XPath; +using Examine; +using Examine.LuceneEngine.Providers; +using Examine.LuceneEngine.SearchCriteria; +using Examine.Providers; +using Examine.SearchCriteria; using umbraco; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -375,7 +381,7 @@ namespace Umbraco.Web /// /// /// - public dynamic Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) + public dynamic Search(ISearchCriteria criteria, BaseSearchProvider searchProvider = null) { return _dynamicContentQuery == null ? new DynamicPublishedContentList( @@ -383,6 +389,51 @@ namespace Umbraco.Web : _dynamicContentQuery.Search(criteria, searchProvider); } + /// + /// Searches content + /// + /// + /// + /// + /// + /// + /// + /// + public IEnumerable TypedSearch(int skip, int take, out int totalRecords, string term, bool useWildCards = true, string searchProvider = null) + { + if (_typedContentQuery != null) return _typedContentQuery.TypedSearch(skip, take, out totalRecords, term, useWildCards, searchProvider); + + var searcher = ExamineManager.Instance.DefaultSearchProvider; + if (string.IsNullOrEmpty(searchProvider) == false) + searcher = ExamineManager.Instance.SearchProviderCollection[searchProvider]; + + //if both are zero, use the native Examine API + if (skip == 0 && take == 0) + { + var results = searcher.Search(term, useWildCards); + totalRecords = results.TotalItemCount; + return results.ConvertSearchResultToPublishedContent(_contentCache); + } + + var luceneSearcher = searcher as BaseLuceneSearcher; + //if the searcher is not a base lucene searcher, we'll have to use Linq Take (edge case) + if (luceneSearcher == null) + { + var results = searcher.Search(term, useWildCards); + totalRecords = results.TotalItemCount; + return results + .Skip(skip) //uses Examine Skip + .ConvertSearchResultToPublishedContent(_contentCache) + .Take(take); //uses Linq Take + } + + //create criteria for all fields + var allSearchFieldCriteria = SearchAllFields(term, useWildCards, luceneSearcher); + + return TypedSearch(skip, take, out totalRecords, allSearchFieldCriteria, searcher); + } + + /// /// Searches content /// @@ -391,16 +442,51 @@ namespace Umbraco.Web /// /// public IEnumerable TypedSearch(string term, bool useWildCards = true, string searchProvider = null) - { - if (_typedContentQuery != null) return _typedContentQuery.TypedSearch(term, useWildCards, searchProvider); - - var searcher = Examine.ExamineManager.Instance.DefaultSearchProvider; - if (string.IsNullOrEmpty(searchProvider) == false) - searcher = Examine.ExamineManager.Instance.SearchProviderCollection[searchProvider]; - - var results = searcher.Search(term, useWildCards); - return results.ConvertSearchResultToPublishedContent(_contentCache); + { + int total; + return TypedSearch(0, 0, out total, term, useWildCards, searchProvider); } + + /// + /// Searhes content + /// + /// + /// + /// + /// + /// + /// + public IEnumerable TypedSearch(int skip, int take, out int totalRecords, ISearchCriteria criteria, BaseSearchProvider searchProvider = null) + { + if (_typedContentQuery != null) return _typedContentQuery.TypedSearch(skip, take, out totalRecords, criteria, searchProvider); + + var s = ExamineManager.Instance.DefaultSearchProvider; + if (searchProvider != null) + s = searchProvider; + + //if both are zero, use the simple native Examine API + if (skip == 0 && take == 0) + { + var r = s.Search(criteria); + totalRecords = r.TotalItemCount; + return r.ConvertSearchResultToPublishedContent(_contentCache); + } + + var maxResults = skip + take; + + var results = s.Search(criteria, + //don't return more results than we need for the paging + //this is the 'trick' - we need to be able to load enough search results to fill + //all items to the maxResults + maxResults: maxResults); + + totalRecords = results.TotalItemCount; + + //use examine to skip, this will ensure the lucene data is not loaded for those items + var records = results.Skip(skip); + + return records.ConvertSearchResultToPublishedContent(_contentCache); + } /// /// Searhes content @@ -410,14 +496,54 @@ namespace Umbraco.Web /// public IEnumerable TypedSearch(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) { - if (_typedContentQuery != null) return _typedContentQuery.TypedSearch(criteria, searchProvider); + var total = 0; + return TypedSearch(0, 0, out total, criteria, searchProvider); + } - var s = Examine.ExamineManager.Instance.DefaultSearchProvider; - if (searchProvider != null) - s = searchProvider; + /// + /// Helper method to create an ISearchCriteria for searching all fields in a + /// + /// + /// + /// + /// + /// + /// This is here because some of this stuff is internal in Examine + /// + private ISearchCriteria SearchAllFields(string searchText, bool useWildcards, BaseLuceneSearcher searcher) + { + var sc = searcher.CreateSearchCriteria(); - var results = s.Search(criteria); - return results.ConvertSearchResultToPublishedContent(_contentCache); + if (_examineGetSearchFields == null) + { + //get the GetSearchFields method from BaseLuceneSearcher + _examineGetSearchFields = typeof(BaseLuceneSearcher).GetMethod("GetSearchFields", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + } + //get the results of searcher.BaseLuceneSearcher() using ugly reflection since it's not public + var searchFields = (IEnumerable)_examineGetSearchFields.Invoke(searcher, null); + + //this is what Examine does internally to create ISearchCriteria for searching all fields + var strArray = searchText.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + sc = useWildcards == false + ? sc.GroupedOr(searchFields, strArray).Compile() + : sc.GroupedOr(searchFields, strArray.Select(x => new CustomExamineValue(Examineness.ComplexWildcard, x.MultipleCharacterWildcard().Value)).ToArray()).Compile(); + return this.Search(sc); + } + + private static MethodInfo _examineGetSearchFields; + + //support class since Examine doesn't expose it's own ExamineValue class publicly + private class CustomExamineValue : IExamineValue + { + public CustomExamineValue(Examineness vagueness, string value) + { + this.Examineness = vagueness; + this.Value = value; + this.Level = 1f; + } + public Examineness Examineness { get; private set; } + public string Value { get; private set; } + public float Level { get; private set; } } #endregion diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index d94476b31c..90bc0a436b 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1251,24 +1251,53 @@ namespace Umbraco.Web public IEnumerable TypedSearch(string term, bool useWildCards = true, string searchProvider = null) { return ContentQuery.TypedSearch(term, useWildCards, searchProvider); - } - - /// - /// Searhes content - /// - /// + } + + /// + /// Searches content + /// + /// + /// + /// + /// + /// /// /// - public IEnumerable TypedSearch(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) + public IEnumerable TypedSearch(int skip, int take, out int totalRecords, string term, bool useWildCards = true, string searchProvider = null) + { + return ContentQuery.TypedSearch(skip, take, out totalRecords, term, useWildCards, searchProvider); + } + + /// + /// Searhes content + /// + /// + /// + /// + /// + /// + /// + public IEnumerable TypedSearch(int skip, int take, out int totalRecords, Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) { - return ContentQuery.TypedSearch(criteria, searchProvider); - } - - #endregion - - #region Xml - - public dynamic ToDynamicXml(string xml) + return ContentQuery.TypedSearch(skip, take, out totalRecords, criteria, searchProvider); + } + + /// + /// Searhes content + /// + /// + /// + /// + public IEnumerable TypedSearch(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) + { + return ContentQuery.TypedSearch(criteria, searchProvider); + } + + #endregion + + #region Xml + + public dynamic ToDynamicXml(string xml) { if (string.IsNullOrWhiteSpace(xml)) return null; var xElement = XElement.Parse(xml); @@ -1439,8 +1468,8 @@ namespace Umbraco.Web /// public IHtmlString Truncate(string html, int length, bool addElipsis, bool treatTagsAsContent) { - return _stringUtilities.Truncate(html, length, addElipsis, treatTagsAsContent); - } + return _stringUtilities.Truncate(html, length, addElipsis, treatTagsAsContent); + } #region Truncate by Words /// @@ -1451,7 +1480,7 @@ namespace Umbraco.Web int length = _stringUtilities.WordsToLength(html, words); return Truncate(html, length, true, false); - } + } /// /// Truncates a string to a given amount of words, can add a elipsis at the end (...). Method checks for open html tags, and makes sure to close them @@ -1481,12 +1510,12 @@ namespace Umbraco.Web int length = _stringUtilities.WordsToLength(html.ToHtmlString(), words); return Truncate(html, length, addElipsis, false); - } - #endregion + } #endregion - + #endregion + #region If - + /// /// If the test is true, the string valueIfTrue will be returned, otherwise the valueIfFalse will be returned. /// From 3d90c2b83f76d398f28500e35cacd5944f5c1971 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 15 Jan 2018 16:11:56 +1100 Subject: [PATCH 2/2] oops, fixed returned type not picked up by compiler due to dynamics --- src/Umbraco.Web/PublishedContentQuery.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 55c7dc6439..8cbbfd3fda 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -527,7 +527,7 @@ namespace Umbraco.Web sc = useWildcards == false ? sc.GroupedOr(searchFields, strArray).Compile() : sc.GroupedOr(searchFields, strArray.Select(x => new CustomExamineValue(Examineness.ComplexWildcard, x.MultipleCharacterWildcard().Value)).ToArray()).Compile(); - return this.Search(sc); + return sc; } private static MethodInfo _examineGetSearchFields;