Skip to main content

Highlights

Another feature called Highlights has been added to RavenDB to enhance the search UX.

Usage

Lets consider a class and index as follows:

public class BlogPost
{
public string Id { get; set; }

public string Title { get; set; }

public string Category { get; set; }

public string Content { get; set; }

public DateTime PublishedAt { get; set; }

public string[] Tags { get; set; }

public BlogComment[] Comments { get; set; }
}
public class BlogComment
{
public string Title { get; set; }

public string Content { get; set; }
}
public class BlogPosts_ByContent : AbstractIndexCreationTask<BlogPost>
{
public class Result
{
public string Content { get; set; }
}

public BlogPosts_ByContent()
{
Map = posts => from post in posts
select new Result
{
Content = post.Content
};

Index(x => x.Content, FieldIndexing.Search);
Store(x => x.Content, FieldStorage.Yes);
TermVector(x => x.Content, FieldTermVector.WithPositionsAndOffsets);
}
}

Now to use Highlights we just need to use one of the Highlight query extension methods. The basic usage can be as simple as:

BlogPost[] results = session
.Advanced
.DocumentQuery<BlogPost, BlogPosts_ByContent>()
.Highlight("Content", 128, 1, out Highlightings highlightings)
.Search("Content", "raven")
.ToArray();

StringBuilder builder = new StringBuilder()
.AppendLine("<ul>");

foreach (BlogPost result in results)
{
string[] fragments = highlightings.GetFragments(result.Id);
builder.AppendLine($"<li>{fragments.First()}</li>");
}

string ul = builder
.AppendLine("</ul>")
.ToString();

This will return the list of results and for each result we will be displaying first found fragment with the length up to 128 characters.

Highlights + Projections

Highlights can also be accessed when projections are performed.

_1
BlogPosts_ByContent.Result[] results = session
.Query<BlogPosts_ByContent.Result, BlogPosts_ByContent>()
.Highlight("Content", 128, 1, new HighlightingOptions
{
PreTags = new[] { "**" },
PostTags = new[] { "**" }
}, out Highlightings highlightings)
.Search(x => x.Content, "raven")
.ProjectInto<BlogPosts_ByContent.Result>()
.ToArray();

Highlights + Map-Reduce

Highlights can be accessed when performing queries on map-reduce indexes.

public class BlogPosts_ByCategory_Content : AbstractIndexCreationTask<BlogPost, BlogPosts_ByCategory_Content.Result>
{
public class Result
{
public string Category { get; set; }

public string Content { get; set; }
}

public BlogPosts_ByCategory_Content()
{
Map = posts => from post in posts
select new Result
{
Category = post.Category,
Content = post.Content
};

Reduce = results => from result in results
group result by result.Category into g
select new
{
Category = g.Key,
Content = string.Join(" ", g.Select(r => r.Content))
};

Index(x => x.Content, FieldIndexing.Search);
Store(x => x.Content, FieldStorage.Yes);
TermVector(x => x.Content, FieldTermVector.WithPositionsAndOffsets);
}
}
_1
// highlighting 'Content', but marking 'Category' as key
BlogPosts_ByCategory_Content.Result[] results = session
.Query<BlogPosts_ByCategory_Content.Result, BlogPosts_ByCategory_Content>()
.Highlight("Content", 128, 1, new HighlightingOptions
{
PreTags = new[] { "**" },
PostTags = new[] { "**" },
GroupKey = "Category"
}, out Highlightings highlightings)
.Search(x => x.Content, "raven")
.ToArray();

// get fragments for 'News' category
var newsHighlightings = highlightings.GetFragments("News");

Customization

IDocumentQuery<T> Highlight(
string fieldName,
int fragmentLength,
int fragmentCount,
out FieldHighlightings highlightings);

IDocumentQuery<T> Highlight(
string fieldName,
string fieldKeyName,
int fragmentLength,
int fragmentCount,
out FieldHighlightings highlightings);

IDocumentQuery<T> Highlight<TValue>(
Expression<Func<T, TValue>> propertySelector,
int fragmentLength,
int fragmentCount,
out FieldHighlightings highlightings);

IDocumentQuery<T> Highlight<TValue>(
Expression<Func<T, TValue>> propertySelector,
Expression<Func<T, TValue>> keyPropertySelector,
int fragmentLength,
int fragmentCount,
out FieldHighlightings highlightings);

where:

  • fieldName or propertySelector is used to mark a field/property for highlight.
  • fieldKeyName or keyPropertySelector is used to mark a field/property key for highlight.
  • fragmentLength this is the maximum length of text fragments that will be returned.
  • fragmentCount this is the maximum number of fragments that will be returned.
  • highlightings this will return an instance of a FieldHighlightings that contains the highlight fragments for each returned result.

By default, the highlighted text is wrapped with <b></b> tags, to change this behavior the SetHighlighterTags method was introduced.

IDocumentQuery<T> SetHighlighterTags(string preTag, string postTag);

IDocumentQuery<T> SetHighlighterTags(string[] preTags, string[] postTags);

Example. To wrap highlighted text with ** we just need to execute following query:

FieldHighlightings highlightings;

BlogPost[] results = session
.Advanced
.DocumentQuery<BlogPost, BlogPosts_ByContent>()
.Highlight("Content", 128, 1, out highlightings)
.SetHighlighterTags("**", "**")
.Search("Content", "raven")
.ToArray();

Default <b></b> tags are coloured and colours are returned in following order:

  • <span style="border-left: 10px solid yellow"> </span>yellow,
  • <span style="border-left: 10px solid lawngreen"> </span>lawngreen,
  • <span style="border-left: 10px solid aquamarine"> </span>aquamarine,
  • <span style="border-left: 10px solid magenta"> </span>magenta,
  • <span style="border-left: 10px solid palegreen"> </span>palegreen,
  • <span style="border-left: 10px solid coral"> </span>coral,
  • <span style="border-left: 10px solid wheat"> </span>wheat,
  • <span style="border-left: 10px solid khaki"> </span>khaki,
  • <span style="border-left: 10px solid lime"> </span>lime,
  • <span style="border-left: 10px solid deepskyblue"> </span>deepskyblue,
  • <span style="border-left: 10px solid deeppink"> </span>deeppink,
  • <span style="border-left: 10px solid salmon"> </span>salmon,
  • <span style="border-left: 10px solid peachpuff"> </span>peachpuff,
  • <span style="border-left: 10px solid violet"> </span>violet,
  • <span style="border-left: 10px solid mediumpurple"> </span>mediumpurple,
  • <span style="border-left: 10px solid palegoldenrod"> </span>palegoldenrod,
  • <span style="border-left: 10px solid darkkhaki"> </span>darkkhaki,
  • <span style="border-left: 10px solid springgreen"> </span>springgreen,
  • <span style="border-left: 10px solid turquoise"> </span>turquoise,
  • <span style="border-left: 10px solid powderblue"> </span>powderblue