Full-Text Search


  • This article is about running a full-text search with a dynamic query.
    To learn how to run a full-text search using a static-index, see full-text search with index.

  • Use the Search() method to query for documents that contain specified term/s within the text of the specified document field/s.

  • When running a full-text search with a dynamic query, the auto-index created by the server breaks down the text of the searched document field using the default search analyzer.
    All generated terms are lower-cased, so the search is case-insensitive.

  • Gain additional control over term tokenization by running a full-text search using a static-index, where the used analyzer is configurable.

  • A boost value can be set for each search to prioritize results. Learn more in boost search results.

  • User experience can be enhanced by requesting text fragments that highlight the searched terms in the results. Learn more in highlight search results.



Search for single term

List<Employee> employees = session
     // Make a dynamic query on Employees collection
    .Query<Employee>()
     // * Call 'Search' to make a Full-Text search
     // * Search is case-insensitive
     // * Look for documents containing the term 'University' within their 'Notes' field
    .Search(x => x.Notes, "University")
    .ToList();

// Results will contain Employee documents that have
// any case variation of the term 'university' in their 'Notes' field.
List<Employee> employees = await asyncSession
     // Make a dynamic query on Employees collection
    .Query<Employee>()
     // * Call 'Search' to make a Full-Text search
     // * Search is case-insensitive
     // * Look for documents containing the term 'University' within their 'Notes' field
    .Search(x => x.Notes, "University")
    .ToListAsync();

// Results will contain Employee documents that have
// any case variation of the term 'university' in their 'Notes' field.
List<Employee> employees = session.Advanced
     // Make a dynamic DocumentQuery on Employees collection
    .DocumentQuery<Employee>()
     // * Call 'Search' to make a Full-Text search
     // * Search is case-insensitive
     // * Look for documents containing the term 'University' within their 'Notes' field
    .Search(x => x.Notes, "University")
    .ToList();

// Results will contain Employee documents that have
// any case variation of the term 'university' in their 'Notes' field.
from "Employees"
where search(Notes, "University")

  • Executing the above query will generate the auto-index Auto/Employees/BySearch(Notes).

  • This auto-index will contain the following two index-fields:

    • Notes
      Contains terms with the original text from the indexed document field 'Notes'.
      Text is lower-cased and Not tokenized.

    • search(Notes)
      Contains lower-cased terms that were tokenized from the 'Notes' field by the default search analyzer (RavenStandardAnalyzer). Calling the Search() method targets these terms to find matching documents.

Search for multiple terms

  • You can search for multiple terms in the same field in a single search method.

  • By default, the logical operator between these terms is 'OR'.

  • This behavior can be modified. See section Search operators.

Pass terms in a string:

List<Employee> employees = session
    .Query<Employee>()
     // * Pass multiple terms in a single string, separated by spaces.
     // * Look for documents containing either 'University' OR 'Sales' OR 'Japanese'
     //   within their 'Notes' field
    .Search(x => x.Notes, "University Sales Japanese")
    .ToList();

// * Results will contain Employee documents that have at least one of the specified terms.
// * Search is case-insensitive.
List<Employee> employees = await asyncSession
    .Query<Employee>()
     // * Pass multiple terms in a single string, separated by spaces.
     // * Look for documents containing either 'University' OR 'Sales' OR 'Japanese'
     //   within their 'Notes' field
    .Search(x => x.Notes, "University Sales Japanese")
    .ToListAsync();

// * Results will contain Employee documents that have at least one of the specified terms.
// * Search is case-insensitive.
List<Employee> employees = session.Advanced
    .DocumentQuery<Employee>()
     // * Pass multiple terms in a single string, separated by spaces.
     // * Look for documents containing either 'University' OR 'Sales' OR 'Japanese'
     //   within their 'Notes' field
    .Search(x => x.Notes, "University Sales Japanese")
    .ToList();

// * Results will contain Employee documents that have at least one of the specified terms.
// * Search is case-insensitive.
from "Employees"
where search(Notes, "University Sales Japanese")

Pass terms in a list:

List<Employee> employees = session
    .Query<Employee>()
     // * Pass terms in IEnumerable<string>.
     // * Look for documents containing either 'University' OR 'Sales' OR 'Japanese'
     //   within their 'Notes' field
    .Search(x => x.Notes, new[] { "University", "Sales", "Japanese" })
    .ToList();

// * Results will contain Employee documents that have at least one of the specified terms.
// * Search is case-insensitive.
List<Employee> employees = await asyncSession
    .Query<Employee>()
     // * Pass terms in IEnumerable<string>.
     // * Look for documents containing either 'University' OR 'Sales' OR 'Japanese'
     //   within their 'Notes' field
    .Search(x => x.Notes, new[] { "University", "Sales", "Japanese" })
    .ToListAsync();

// * Results will contain Employee documents that have at least one of the specified terms.
// * Search is case-insensitive.
from "Employees"
where search(Notes, "University Sales Japanese")

Search in multiple fields

  • You can search for terms in different fields by making multiple search calls.

  • By default, the logical operator between consecutive search methods is 'OR'.

  • This behavior can be modified. See section Search options.

List<Employee> employees = session
    .Query<Employee>()
     // * Look for documents containing:
     //   'French' in their 'Notes' field OR 'President' in their 'Title' field
    .Search(x => x.Notes, "French")
    .Search(x => x.Title, "President")
    .ToList();

// * Results will contain Employee documents that have
//   at least one of the specified fields with the specified terms.
// * Search is case-insensitive.
List<Employee> employees = await asyncSession
    .Query<Employee>()
     // * Look for documents containing:
     //   'French' in their 'Notes' field OR 'President' in their 'Title' field
    .Search(x => x.Notes, "French")
    .Search(x => x.Title, "President")
    .ToListAsync();

// * Results will contain Employee documents that have
//   at least one of the specified fields with the specified terms.
// * Search is case-insensitive.
List<Employee> employees = session.Advanced
    .DocumentQuery<Employee>()
     // * Look for documents containing:
     //   'French' in their 'Notes' field OR 'President' in their 'Title' field
    .Search(x => x.Notes, "French")
    .Search(x => x.Title, "President")
    .ToList();

// * Results will contain Employee documents that have
//   at least one of the specified fields with the specified terms.
// * Search is case-insensitive.
from "Employees"
where (search(Notes, "French") or search(Title, "President"))

Search in complex object

  • You can search for terms within a complex object.

  • Any nested text field within the object is searchable.

List<Company> companies = session
    .Query<Company>()
     // * Look for documents that contain:
     //   the term 'USA' OR 'London' in any field within the complex 'Address' object
    .Search(x => x.Address, "USA London")
    .ToList();

// * Results will contain Company documents that are located either in 'USA' OR in 'London'.
// * Search is case-insensitive.
List<Company> companies = await asyncSession
    .Query<Company>()
     // * Look for documents that contain:
     //   the term 'USA' OR 'London' in any field within the complex 'Address' object
    .Search(x => x.Address, "USA London")
    .ToListAsync();

// * Results will contain Company documents that are located either in 'USA' OR in 'London'.
// * Search is case-insensitive.
List<Company> companies = session.Advanced
    .DocumentQuery<Company>()
     // * Look for documents that contain:
     //   the term 'USA' OR 'London' in any field within the complex 'Address' object
    .Search(x => x.Address, "USA London")
    .ToList();

// * Results will contain Company documents that are located either in 'USA' OR in 'London'.
// * Search is case-insensitive.
from "Companies"
where search(Address, "USA London")

Search operators

  • By default, the logical operator between multiple terms within the same field in a search call is OR.

  • This can be modified using the @operator parameter as follows:

AND:

List<Employee> employees = session
    .Query<Employee>()
     // * Pass `@operator` with 'SearchOperator.And'
    .Search(x => x.Notes, "College German", @operator: SearchOperator.And)
    .ToList();

// * Results will contain Employee documents that have BOTH 'College' AND 'German'
//   in their 'Notes' field.
// * Search is case-insensitive.
List<Employee> employees = await asyncSession
    .Query<Employee>()
     // * Pass `@operator` with 'SearchOperator.And'
    .Search(x => x.Notes, "College German", @operator: SearchOperator.And)
    .ToListAsync();

// * Results will contain Employee documents that have BOTH 'College' AND 'German'
//   in their 'Notes' field.
// * Search is case-insensitive.
List<Employee> employees = session.Advanced
    .DocumentQuery<Employee>()
     // * Pass `@operator` with 'SearchOperator.And'
    .Search(x => x.Notes, "College German", @operator: SearchOperator.And)
    .ToList();

// * Results will contain Employee documents that have BOTH 'College' AND 'German'
//   in their 'Notes' field.
// * Search is case-insensitive.
from "Employees"
where search(Notes, "College German", and)

OR:

List<Employee> employees = session
    .Query<Employee>()
     // * Pass `@operator` with 'SearchOperator.Or' (or don't pass this param at all)
    .Search(x => x.Notes, "College German", @operator: SearchOperator.Or)
    .ToList();

// * Results will contain Employee documents that have EITHER 'College' OR 'German'
//   in their 'Notes' field.
// * Search is case-insensitive.
List<Employee> employees = await asyncSession
    .Query<Employee>()
     // * Pass `@operator` with 'SearchOperator.Or' (or don't pass this param at all)
    .Search(x => x.Notes, "College German", @operator: SearchOperator.Or)
    .ToListAsync();

// * Results will contain Employee documents that have EITHER 'College' OR 'German'
//   in their 'Notes' field.
// * Search is case-insensitive.
List<Employee> employees = session.Advanced
    .DocumentQuery<Employee>()
     // * Pass `@operator` with 'SearchOperator.Or' (or don't pass this param at all)
    .Search(x => x.Notes, "College German", @operator: SearchOperator.Or)
    .ToList();

// * Results will contain Employee documents that have EITHER 'College' OR 'German'
//   in their 'Notes' field.
// * Search is case-insensitive.
from "Employees"
where search(Notes, "College German")

Search options

  • Search options allow to:

    • Negate a search criteria.
    • Specify the logical operator used between consecutive search calls.
  • When using Query: use the options parameter.
    When using DocumentQuery: follow the specific syntax in each example below.

Negate search:

List<Company> companies = session
    .Query<Company>()
     // Pass 'options' with 'SearchOptions.Not'
    .Search(x => x.Address, "USA", options: SearchOptions.Not)
    .ToList();

// * Results will contain Company documents are NOT located in 'USA'
// * Search is case-insensitive
List<Company> companies = await asyncSession
    .Query<Company>()
     // Pass 'options' with 'SearchOptions.Not'
    .Search(x => x.Address, "USA", options: SearchOptions.Not)
    .ToListAsync();

// * Results will contain Company documents are NOT located in 'USA'
// * Search is case-insensitive
List<Company> companies = session.Advanced
    .DocumentQuery<Company>()
    .OpenSubclause()
     // Call 'Not' to negate the next search call
    .Not
    .Search(x => x.Address, "USA")
    .CloseSubclause()
    .ToList();

// * Results will contain Company documents are NOT located in 'USA'
// * Search is case-insensitive
from "Companies"
where (exists(Address) and not search(Address, "USA"))

Default behavior between search calls:

  • By default, the logical operator between consecutive search methods is OR.

List<Company> companies = session
    .Query<Company>()
    .Where(x => x.Contact.Title == "Owner")
     // Operator AND will be used with previous 'Where' predicate
    .Search(x => x.Address.Country, "France")
     // Operator OR will be used between the two 'Search' calls by default
    .Search(x => x.Name, "Markets")
    .ToList();

// * Results will contain Company documents that have:
//   ('Owner' as the 'Contact.Title')
//   AND
//   (are located in 'France' OR have 'Markets' in their 'Name' field)
//
// * Search is case-insensitive
List<Company> companies = await asyncSession
    .Query<Company>()
    .Where(x => x.Contact.Title == "Owner")
     // Operator AND will be used with previous 'Where' predicate
    .Search(x => x.Address.Country, "France")
     // Operator OR will be used between the two 'Search' calls by default
    .Search(x => x.Name, "Markets")
    .ToListAsync();

// * Results will contain Company documents that have:
//   ('Owner' as the 'Contact.Title')
//   AND
//   (are located in 'France' OR have 'Markets' in their 'Name' field)
//
// * Search is case-insensitive
List<Company> companies = session.Advanced
    .DocumentQuery<Company>()
    .WhereEquals(x => x.Contact.Title, "Owner")
     // Operator AND will be used with previous 'Where' predicate
     // Call 'OpenSubclause' to open predicate block
    .OpenSubclause()
    .Search(x => x.Address.Country, "France")
     // Operator OR will be used between the two 'Search' calls by default
    .Search(x => x.Name, "Markets")
     // Call 'CloseSubclause' to close predicate block
    .CloseSubclause()
    .ToList();

// * Results will contain Company documents that have:
//   ('Owner' as the 'Contact.Title')
//   AND
//   (are located in 'France' OR have 'Markets' in their 'Name' field)
//
// * Search is case-insensitive
from "Companies" 
where Contact.Title == "Owner" and
(search(Address.Country, "France") or search(Name, "Markets"))

AND search calls:

List<Employee> employees = session
    .Query<Employee>()
    .Search(x => x.Notes, "French")
     // * Pass 'options' with 'SearchOptions.And' to the second 'Search'
     // * Operator AND will be used with previous the 'Search' call
    .Search(x => x.Title, "Manager", options: SearchOptions.And)
    .ToList();

// * Results will contain Employee documents that have:
//   ('French' in their 'Notes' field)
//   AND
//   ('Manager' in their 'Title' field)
//
// * Search is case-insensitive
List<Employee> employees = await asyncSession
    .Query<Employee>()
    .Search(x => x.Notes, "French")
     // * Pass 'options' with 'SearchOptions.And' to this second 'Search'
     // * Operator AND will be used with previous the 'Search' call
    .Search(x => x.Title, "Manager", options: SearchOptions.And)
    .ToListAsync();

// * Results will contain Employee documents that have:
//   ('French' in their 'Notes' field)
//   AND
//   ('Manager' in their 'Title' field)
//
// * Search is case-insensitive
List<Employee> employees = session.Advanced
    .DocumentQuery<Employee>()
    .Search(x => x.Notes, "French")
     // Call 'AndAlso' so that operator AND will be used with previous 'Search' call
    .AndAlso()
    .Search(x => x.Title, "Manger")
    .ToList();

// * Results will contain Employee documents that have:
//   ('French' in their 'Notes' field)
//   AND
//   ('Manager' in their 'Title' field)
//
// * Search is case-insensitive
from "Employees" 
where search(Notes, "French") and search(Title, "Manager")

Use options as bit flags:

List<Employee> employees = session
    .Query<Employee>()
    .Search(x => x.Notes, "French")
     // Pass logical operators as flags in the 'options' parameter
    .Search(x => x.Title, "Manager", options: SearchOptions.Not | SearchOptions.And)
    .ToList();

// * Results will contain Employee documents that have:
//   ('French' in their 'Notes' field)
//   AND
//   (do NOT have 'Manager' in their 'Title' field)
//
// * Search is case-insensitive
List<Employee> employees = await asyncSession
    .Query<Employee>()
    .Search(x => x.Notes, "French")
     // Pass logical operators as flags in the 'options' parameter
    .Search(x => x.Title, "Manager", options: SearchOptions.Not | SearchOptions.And)
    .ToListAsync();

// * Results will contain Employee documents that have:
//   ('French' in their 'Notes' field)
//   AND
//   (do NOT have 'Manager' in their 'Title' field)
//
// * Search is case-insensitive
List<Employee> employees = session.Advanced
    .DocumentQuery<Employee>()
    .Search(x => x.Notes, "French")
     // Call 'AndAlso' so that operator AND will be used with previous 'Search' call
    .AndAlso()
    .OpenSubclause()
     // Call 'Not' to negate the next search call
    .Not
    .Search(x => x.Title, "Manager")
    .CloseSubclause()
    .ToList();

// * Results will contain Employee documents that have:
//   ('French' in their 'Notes' field)
//   AND
//   (do NOT have 'Manager' in their 'Title' field)
//
// * Search is case-insensitive
from "Employees"
where search(Notes, "French") and
(exists(Title) and not search(Title, "Manager"))

Using wildcards

  • Wildcards can be used to replace:

    • Prefix of a searched term
    • Postfix of a searched term
    • Both prefix & postfix
  • Note:

    • Searching with a wildcard as the prefix of the term (e.g. *text) is less recommended,
      as it will cause the server to perform a full index scan.

    • Instead, consider using a static-index that indexes the field in reverse order
      and then query with a wildcard as the postfix, which is much faster.

List<Employee> employees = session
    .Query<Employee>()
     // Use '*' to replace one ore more characters
    .Search(x => x.Notes, "art*")
    .Search(x => x.Notes, "*logy")
    .Search(x => x.Notes, "*mark*")
    .ToList();

// Results will contain Employee documents that have in their 'Notes' field:
// (terms that start with 'art')  OR
// (terms that end with 'logy') OR
// (terms that have the text 'mark' in the middle) 
//
// * Search is case-insensitive
List<Employee> employees = await asyncSession
    .Query<Employee>()
     // Use '*' to replace one ore more characters
    .Search(x => x.Notes, "art*")
    .Search(x => x.Notes, "*logy")
    .Search(x => x.Notes, "*mark*")
    .ToListAsync();

// Results will contain Employee documents that have in their 'Notes' field:
// (terms that start with 'art')  OR
// (terms that end with 'logy') OR
// (terms that have the text 'mark' in the middle) 
//
// * Search is case-insensitive
List<Employee> employees = session.Advanced
    .DocumentQuery<Employee>()
     // Use '*' to replace one ore more characters
    .Search(x => x.Notes, "art*")
    .Search(x => x.Notes, "*logy")
    .Search(x => x.Notes, "*mark*")
    .ToList();

// Results will contain Employee documents that have in their 'Notes' field:
// (terms that start with 'art')  OR
// (terms that end with 'logy') OR
// (terms that have the text 'mark' in the middle) 
//
// * Search is case-insensitive
from "Employees" where
search(Notes, "art*") or
search(Notes, "*logy") or
search(Notes, "*mark*")

Syntax

// Query overloads:
// ================

IRavenQueryable<T> Search<T>(
    Expression<Func<T, object>> fieldSelector,
    string searchTerms,
    decimal boost,
    SearchOptions options,
    SearchOperator @operator);

IRavenQueryable<T> Search<T>(
    Expression<Func<T, object>> fieldSelector,
    IEnumerable<string> searchTerms,
    decimal boost,
    SearchOptions options,
    SearchOperator @operator);

// DocumentQuery overloads:
// ========================

IDocumentQueryBase<T> Search(
    string fieldName,
    string searchTerms,
    SearchOperator @operator);

IDocumentQueryBase<T> Search<TValue>(
    Expression<Func<T, TValue>> propertySelector,
    string searchTerms,
    SearchOperator @operator);
Parameter Type Description
fieldSelector Expression<Func<TResult>> Points to the field in which you search.
fieldName string Name of the field in which you search.
searchTerms string /
IEnumerable<string>
A string containing the term or terms (separated by spaces) to search for.
Or, can pass an array (or other IEnumerable) with terms to search for.
boost decimal The boost value.
Learn more in boost search results.
Default is 1.0
options SearchOptions enum Logical operator to use between consecutive Search methods.
Can be Or, And, Not, or Guess.
Default is SearchOptions.Guess
@operator SearchOperator enum Logical operator to use between multiple terms in the same Search method.
Can be Or or And.
Default is SearchOperation.Or