Create RavenDB indexes for efficient document queries (project)
Create RavenDB indexes for efficient document queries in NovaTune.
src/NovaTuneApp/NovaTuneApp.ApiService/Infrastructure/RavenDb/Indexes/{Collection}_{By|For}{Criteria}.csUsers_ByEmail.cs, Tracks_ByUserForSearch.csLocation: src/NovaTuneApp/NovaTuneApp.ApiService/Infrastructure/RavenDb/Indexes/{IndexName}.cs
using NovaTuneApp.ApiService.Models;
using Raven.Client.Documents.Indexes;
namespace NovaTuneApp.ApiService.Infrastructure.RavenDb.Indexes;
/// <summary>
/// RavenDB index for {description}.
/// </summary>
public class Tracks_ByUserForSearch : AbstractIndexCreationTask<Track>
{
public Tracks_ByUserForSearch()
{
Map = tracks => from track in tracks
where track.Status != TrackStatus.Unknown
select new
{
track.UserId,
track.Status,
track.Title,
track.Artist,
track.CreatedAt,
track.UpdatedAt,
track.Duration,
SearchText = new[] { track.Title, track.Artist }
};
// For full-text search
Index("SearchText", FieldIndexing.Search);
Analyze("SearchText", "StandardAnalyzer");
}
}
Indexes are automatically deployed when using IndexCreation.CreateIndexes():
// In Program.cs or a startup extension
var store = services.GetRequiredService<IDocumentStore>();
await IndexCreation.CreateIndexesAsync(
typeof(Tracks_ByUserForSearch).Assembly,
store);
Or register individual indexes:
await new Tracks_ByUserForSearch().ExecuteAsync(store);
// Use the index explicitly
var tracks = await session
.Query<Track, Tracks_ByUserForSearch>()
.Where(t => t.UserId == userId)
.Where(t => t.Status != TrackStatus.Deleted)
.OrderByDescending(t => t.CreatedAt)
.Take(20)
.ToListAsync(ct);
// Full-text search
var searchResults = await session
.Query<Track, Tracks_ByUserForSearch>()
.Search(t => t.Title, searchTerm)
.Search(t => t.Artist, searchTerm, options: SearchOptions.Or)
.ToListAsync(ct);
public class Users_ByEmail : AbstractIndexCreationTask<ApplicationUser>
{
public Users_ByEmail()
{
Map = users => from user in users
select new { user.NormalizedEmail };
}
}
public class UploadSessions_ByUserAndStatus : AbstractIndexCreationTask<UploadSession>
{
public UploadSessions_ByUserAndStatus()
{
Map = sessions => from session in sessions
select new
{
session.UserId,
session.Status,
session.ExpiresAt
};
}
}
public class Tracks_ByUserForSearch : AbstractIndexCreationTask<Track>
{
public Tracks_ByUserForSearch()
{
Map = tracks => from track in tracks
select new
{
track.UserId,
SearchText = new[] { track.Title, track.Artist }
};
Index("SearchText", FieldIndexing.Search);
Analyze("SearchText", "StandardAnalyzer");
}
}
public class Tracks_ByScheduledDeletion : AbstractIndexCreationTask<Track>
{
public Tracks_ByScheduledDeletion()
{
Map = tracks => from track in tracks
where track.Status == TrackStatus.Deleted
&& track.ScheduledDeletionAt != null
select new
{
track.Status,
track.ScheduledDeletionAt
};
}
}
| Pattern | Example | Use Case |
|---|---|---|
{Collection}_By{Field} | Users_ByEmail | Single-field lookup |
{Collection}_By{Field}And{Field} | Sessions_ByUserAndStatus | Multi-field lookup |
{Collection}_For{Purpose} | Tracks_ForSearch | Special-purpose index |
{Collection}_By{Field}For{Purpose} | Tracks_ByUserForSearch | Combined |
WaitForNonStaleResults() when needed[Fact]
public async Task Index_Should_ReturnUserTracks_FilteredByStatus()
{
// Arrange
var track = new Track { UserId = "user1", Status = TrackStatus.Ready };
await session.StoreAsync(track);
await session.SaveChangesAsync();
// Act
var results = await session
.Query<Track, Tracks_ByUserForSearch>()
.Where(t => t.UserId == "user1")
.Where(t => t.Status == TrackStatus.Ready)
.ToListAsync();
// Assert
results.Should().ContainSingle();
}