Use nested queries in Find

  • Feb 12, 2016
  • EPiServer
  • EPiServer Find
  • Nested Queries
  • Elastic Search
  • |

Since EPiServer released update 89 it is possible to use nested queries in EPiServer Find. Nested queries is a feature from Elastic Search that is now also supported by Find. With nested queries it is possible to query on a list that is defined on a type. For example, an Employees list property that belongs to a Company type. To demo this functionality, I’ve extended my Find demo site. It’s worth having a look at this demo site because it contains a number of Find features:

  • Typed search
  • Unified search
  • Statistics (autocomplete, related queries, spellcheck)
  • Best bets, synonyms, auto boosting, boosting with weights, boosting with filters
  • More like (related search)
  • Highlighting
  • Nested queries

Of course, if you have some Find feature suggestions, please let me know. I’m happy to implement your suggestion as well Glimlach.

Nested queries supports a number of functionalities:

  • Filtering
  • Facets
  • Ordering

In my demo application I’ve defined a Company type:

public class Company
        {
            public Guid Key { get; set; }
            public string Name { get; set; }
            public string CatchPhrase { get; set; }
            public string Bs { get; set; }
            public string Phone { get; set; }
            public string Street { get; set; }
            public string Zipcode { get; set; }
            public string City { get; set; }
            public List<Employee> Employees { get; set; } 
     
            public virtual string SearchTitle { get { return Name; } }
            public virtual string SearchText
            {
                get
                {
                    return string.Format("{0} {1} {2} {3} {4} {5}", CatchPhrase, Bs, Phone, Street, Zipcode, City);
                }
            }
            public virtual string SearchSection { get { return "Company"; } }
        }

 And an Employee type.

public class Employee
        {
            public Guid Key { get; set; }
            public string Name { get; set; }
            public string Username { get; set; }
            public string EmailAddress { get; set; }
            public string Phone { get; set; }
            public string Street { get; set; }
            public string Zipcode { get; set; }
            public string City { get; set; }
            public string Country { get; set; }
            public int Age { get; set; }
     
            public virtual string SearchTitle { get { return Name; } }
            public virtual string SearchText 
            {
                get
                {
                    return string.Format("{0} {1} {2} {3} {4} {5}", Username, EmailAddress, Phone, Street, Zipcode, City);
                } 
            }
            public virtual string SearchSection { get { return "Employee"; } }
        }

 As you can see in the code snippet the Company type has a Employee list property. We can now use nested queries to query on the list property.

Before we start coding the query, first we need to add a convention. Conventions need to be set when your web application is starting up so a good place is an initialize module or the global.asax.

SearchClient.Instance.Conventions.NestedConventions.Add<Company>(company => company.Employees);

If you forget the convention, an error is thrown.

{"The remote server returned an error: (400) Bad Request.\r\nSearchPhaseExecutionException[Failed to execute phase [query], all shards failed;

 The final step is creating the nested query. The Find APi includes some extension methods for nested queries that can be found in the following classes:

  • NestedFacetExtensions
  • NestedSortingExtensions
  • NestedFilterExtensions
Facets

Let’s start with creating some facets based on properties of the Employee type.

var result = _client.Search<Company>()
                    .TermsFacetFor(c => c.Employees, c => c.Country)
                    .HistogramFacetFor(c => c.Employees, c => c.Age, 10)
                    .GetResult();
     
    var termsFacet = result.TermsFacetFor(c => c.Employees, c => c.Country);
    var histogramFacet = result.HistogramFacetFor(c => c.Employees, c => c.Age);
I’ve created two facets in the example. First, a facet based on the country property, second, a histogram facet with an interval of 10 on the age property of the Employee type.

 

Filters

The next example shows how you can use filtering on nested list properties. In this case, the employees list. The first filter checks if a company has at least, one employee from a given country. The second filter checks if the employee age is between a certain range.

var result = _client.Search<Company>()
                    .Filter(c => c.Employees, c => c.Country.MatchCaseInsensitive(country))
                    .Filter(c => c.Employees, c => c.Age.InRange(startNumber, endNumber));
                    .GetResult();

 

Ordering

It’s important to understand what ordering actually means when applying that on nested queries. If we look at the example below I’m using ordering on the Name property of the Employee type. That does not mean that the Employee objects in the child list property of the Company type are ordered by Name. The order functionality reflects on the Company objects. For example, company A has two employees Richard and Ted, company B also has two employees Anna and Simon. If I’m using the code sample below then company B is ordered first in the list.

var result = _client.Search<Company>()
                    .Filter(c => c.Employees, c => c.Country.MatchCaseInsensitive(country));
                    .OrderBy(c => c.Employees, c => c.Name, c => c.Country.MatchCaseInsensitive(country))
                    .GetResult();

Important to understand is that the expression passed in the OrderBy method is also used in a filter method, otherwise an exception is thrown. You can learn more about this in thedocumentation of elastic search.

You can view the code samples on my GitHub account.

 

Comments