Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Owain Williams 326 posts 978 karma points MVP c-trib
    Feb 17, 2017 @ 12:20
    Owain Williams
    0

    Fuzzy Search - Examine Lucene

    I have been trying to get my search box to allow for Fuzzy search but I think I must be missing something somewhere. I've been following the tutorial from codeshare.co.uk on advanced searches and the search works as expected but now I am trying to extend this.

    I thought it would be as simple as adding .Fuzzy(0.5f).Value to my SearchTerm but it doesn't seem to be the case.

    The search continues to work but if I type 'tree' for example, I would expect to see results for 'trees'.

    This is my controller code - which is idential to the codeshare examples other than the addition of a namespace and fuzzy to my HttpPost section where I've added Fuzzy.

        using System.Web.Mvc;
    using Umbraco.Web.Mvc;
    using wildsite.Models;
    using Umbraco.Web;
    using wildsite.Helpers;
    using System.Collections.Generic;
    using Examine.LuceneEngine.SearchCriteria;
    
    namespace wildsite.Controllers
    {
        public class SearchController : SurfaceController
        {
            #region Private Variables and Methods
    
            private SearchHelper _searchHelper { get { return new SearchHelper(new UmbracoHelper(UmbracoContext.Current)); } }
    
            private string PartialViewPath(string name)
            {
                return $"~/Views/Partials/Search/{name}.cshtml";
            }
    
            private List<SearchGroup> GetSearchGroups(SearchViewModel model)
            {
                List<SearchGroup> searchGroups = null;
                if (!string.IsNullOrEmpty(model.FieldPropertyAliases))
                {
                    searchGroups = new List<SearchGroup>();
                    searchGroups.Add(new SearchGroup(model.FieldPropertyAliases.Split(','), new string[] { model.SearchTerm }));
                }
                return searchGroups;
            }
    
            #endregion
    
            #region Controller Actions
    
            [HttpGet]
            public ActionResult RenderSearchForm(string query, string docTypeAliases, string fieldPropertyAliases, int pageSize, int pagingGroupSize)
            {
                SearchViewModel model = new SearchViewModel();
                if (!string.IsNullOrEmpty(query))
                {
    
                    model.SearchTerm = query;
                    model.DocTypeAliases = docTypeAliases;
                    model.FieldPropertyAliases = fieldPropertyAliases;
                    model.PageSize = pageSize;
                    model.PagingGroupSize = pagingGroupSize;
                    model.SearchGroups = GetSearchGroups(model);
                    model.SearchResults = _searchHelper.GetSearchResults(model, Request.Form.AllKeys);
                }
                return PartialView(PartialViewPath("_SearchForm"), model);
            }
    
    
    
            [HttpPost]
            public ActionResult SubmitSearchForm(SearchViewModel model)
            {
                if (ModelState.IsValid)
                {
                    if (!string.IsNullOrEmpty(model.SearchTerm))
                    {
                        model.SearchTerm = model.SearchTerm.Fuzzy(0.1f).Value;
                        model.SearchGroups = GetSearchGroups(model);
                        model.SearchResults = _searchHelper.GetSearchResults(model, Request.Form.AllKeys);
                    }
                    return RenderSearchResults(model.SearchResults);
                }
                return null;
            }
    
            public ActionResult RenderSearchResults(SearchResultsModel model)
            {
                return PartialView(PartialViewPath("_SearchResults"), model);
            }
    
            #endregion
    
        }
    }
    

    Can anyone steer me in the right direction of where I've gone wrong?

    Thanks,

  • Steven Harland 78 posts 491 karma points c-trib
    Feb 17, 2017 @ 21:35
    Steven Harland
    101

    Hi Owain,

    Here's one way to do it.

    Modify your SearchGroup class so that SearchTerms becomes an array of IExamineValue instead of string:

    using Examine.SearchCriteria;
    
    namespace CodeShare.Library.Models
    {
        public class SearchGroup
        {
            public string[] FieldsToSearchIn { get; set; }
            public IExamineValue[] SearchTerms { get; set; }
    
            public SearchGroup(string[] fieldsToSearchIn, IExamineValue[] searchTerms)
            {
                FieldsToSearchIn = fieldsToSearchIn;
                SearchTerms = searchTerms;
            }
        }
    }
    

    Then update your GetSearchGroups method as follows:

    private List<SearchGroup> GetSearchGroups(SearchViewModel model)
    {
        List<SearchGroup> searchGroups = null;
        if (!string.IsNullOrEmpty(model.FieldPropertyAliases))
        {
            searchGroups = new List<SearchGroup>();
            searchGroups.Add(new SearchGroup(model.FieldPropertyAliases.Split(','), new IExamineValue[] { model.SearchTerm.Fuzzy(0.5f) }));
        }
        return searchGroups;
    }
    

    Note the call to Fuzzy here which returns an IExamineValue.

    Finally, you can remove this line:

    model.SearchTerm = model.SearchTerm.Fuzzy(0.1f).Value;
    

    Hope this helps.

    Steven

  • Owain Williams 326 posts 978 karma points MVP c-trib
    Feb 27, 2017 @ 11:33
    Owain Williams
    0

    Works perfectly - thanks for your help!

  • Stefano 52 posts 295 karma points c-trib
    Feb 18, 2017 @ 10:46
    Stefano
    0

    I use this helper class to do fuzzy partial searches

    public static class Examine
    {
        /// <summary>
        ///     Builds and run the Lucene query.
        /// </summary>
        public static ISearchResults Search(string query)
        {
            var searcher = ExamineManager.Instance.SearchProviderCollection["ExternalSearcher"];
            var luceneQuery = new StringBuilder();
    
            if (!string.IsNullOrWhiteSpace(query))
            {
                // Split the words so that it will search for each with an OR. It's necessary because
                // name and surname are in two different fields.
                var searchTerms = query
                    .Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries)
                    .Select(QueryParser.Escape)
                    .SelectMany(st => new[] {st + "*", st + "~0.6"});
    
                var byQuery = Search(searchTerms);
                luceneQuery.And(byQuery);
            }
    
            ISearchResults searchResults;
            // If no filters were selected search for nodes with ID 0 that will return no results.
            if (luceneQuery.Length > 0)
            {
                var rawQuery = searcher.CreateSearchCriteria().RawQuery(luceneQuery.ToString());
                searchResults = searcher.Search(rawQuery);
            }
            else
            {
                searchResults = searcher.Search(searcher.CreateSearchCriteria().Id(0).Compile());
            }
    
            return searchResults;
        }
    
        private static string Search(IEnumerable<string> queries)
        {
            return string.Join(" ", queries.Select(st => $"{st}"));
        }
    
        /// <summary>
        ///     Wraps the query with "+(" and ")" while adding it to the luceneQuery.
        /// </summary>
        private static void And(this StringBuilder luceneQuery, string query)
        {
            luceneQuery.Append("+(");
            luceneQuery.Append(query);
            luceneQuery.Append(")");
        }
    }
    
  • Owain Williams 326 posts 978 karma points MVP c-trib
    Feb 20, 2017 @ 15:26
    Owain Williams
    0

    Thanks! I've not had a chance to test this yet, hopefully tomorrow.

    Appreciate the time you've taken though. Fingers crossed it's what I've been missing :)

  • sam woodcock 1 post 71 karma points
    Jul 17, 2018 @ 13:00
    sam woodcock
    0

    how can i limit what Fields the query can look in?

    ive tried passing the field in luceneQuery.Append("+"+field+":(");

    but debugging the query in Luke no results get returned?

  • Owain Williams 326 posts 978 karma points MVP c-trib
    Jul 17, 2018 @ 14:39
    Owain Williams
    0

    Try this:

    luceneQuery.Append("+("+ field +")");
    

    O.

  • Stefano 52 posts 295 karma points c-trib
    Feb 27, 2017 @ 11:59
    Stefano
    0

    Notice I've hard coded 0.6 fuzziness, it worked well for me but you may want to tweak it. I used it to match name and surnames.

  • Owain Williams 326 posts 978 karma points MVP c-trib
    Feb 27, 2017 @ 12:00
    Owain Williams
    0

    Ye, I've tweaked it slightly but may try and make it editable via the backoffice rather than hardcode.

Please Sign in or register to post replies

Write your reply to:

Draft