see on GitHub

Querying : Highlighting

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

Setup

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
                       {
                           post.Content
                       };

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

Important

Each of the fields on which we want to use Highlighting needs to have:

  • FieldIndexing set to Search
  • FieldStorage set to Yes
  • FieldTermVector set to WithPositionsAndOffsets

Usage

To use Highlighting we just need to use one of the Highlight query 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.

Highlighting + Projections

Highlighting can also be done when projections are performed.

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();
BlogPosts_ByContent.Result[] results = session
    .Advanced
    .DocumentQuery<BlogPost, BlogPosts_ByContent>()
    .Highlight("Content", 128, 1, new HighlightingOptions
    {
        PreTags = new[] { "**" },
        PostTags = new[] { "**" }
    }, out Highlightings highlightings)
    .Search("Content", "raven")
    .SelectFields<BlogPosts_ByContent.Result>()
    .ToArray();
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
                       {
                           post.Content
                       };

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

Highlighting + Map-Reduce

Highlighting can be performed when executing queries on map-reduce indexes.

// 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");
// highlighting 'Content', but marking 'Category' as key
BlogPosts_ByCategory_Content.Result[] results = session
    .Advanced
    .DocumentQuery<BlogPosts_ByCategory_Content.Result, BlogPosts_ByCategory_Content>()
    .Highlight("Content", 128, 1, new HighlightingOptions
    {
        PreTags = new[] { "**" },
        PostTags = new[] { "**" },
        GroupKey = "Category"
    }, out Highlightings highlightings)
    .Search("Content", "raven")
    .ToArray();

// get fragments for 'News' category
var newsHighlightings = highlightings.GetFragments("News");
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
                       {
                           post.Category,
                           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);
    }
}

Remarks

Note

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

  •  yellow,
  •  lawngreen,
  •  aquamarine,
  •  magenta,
  •  palegreen,
  •  coral,
  •  wheat,
  •  khaki,
  •  lime,
  •  deepskyblue,
  •  deeppink,
  •  salmon,
  •  peachpuff,
  •  violet,
  •  mediumpurple,
  •  palegoldenrod,
  •  darkkhaki,
  •  springgreen,
  •  turquoise,
  •  powderblue