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 Result
{
Content = 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 Result
{
Content = 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 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);
}
}
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