Skip to content

Commit e12c58d

Browse files
committed
EF-281: Support building "Atlas" search indexes
Fluent API for building search indexes.
1 parent 4fb0fdf commit e12c58d

File tree

89 files changed

+17449
-92
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+17449
-92
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ Entity Framework Core and MongoDB have a wide variety of features. This provider
9090
- `CamelCaseElementNameConvention` for helping map Pascal-cased C# properties to camel-cased BSON elements
9191
- Type discriminators including `OfType<T>` and `Where(e => e is T)`
9292
- Support for EF shadow properties and EF.Proxy for navigation traversal
93+
- MongoDB full text search
94+
- MongoDB vector search
9395
- [Client Side Field Level Encryption](https://www.mongodb.com/docs/manual/core/csfle/quick-start/) and [Queryable Encryption](https://www.mongodb.com/docs/manual/core/queryable-encryption/) compatibility
9496

9597
## Limitations
@@ -103,7 +105,6 @@ in the mean-time consider using the existing [MongoDB C# Driver's](https://githu
103105
- GroupBy operations
104106
- Includes/joins
105107
- Geospatial
106-
- Atlas search
107108
- ExecuteUpdate & ExecuteDelete bulk operations (EF 9 only)
108109

109110
### Not supported, out-of-scope features
@@ -119,7 +120,7 @@ in the mean-time consider using the existing [MongoDB C# Driver's](https://githu
119120
## Breaking changes
120121

121122
This project's version-numbers are aligned with Entity Framework Core and as-such we can not use the semver convention of constraining breaking changes solely to major version numbers. Please keep an eye on our [Breaking Changes](/BREAKING-CHANGES.md) document before upgrading to a new version of this provider.
122-
123+
123124
## Documentation
124125

125126
- [MongoDB](https://www.mongodb.com/docs)

src/MongoDB.EntityFrameworkCore/Diagnostics/MongoEventId.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ private enum Id
5050
VectorSearchNeedsIndex,
5151
VectorSearchReturnedZeroResults,
5252
WaitingForVectorIndex,
53+
WaitingForSearchIndex,
5354
}
5455

5556
private static EventId MakeDatabaseCommandId(Id id)
@@ -226,4 +227,13 @@ private static EventId MakeDatabaseId(Id id)
226227
/// <para>This event uses the <see cref="TimeSpanEventData" /> payload when used with a <see cref="DiagnosticSource" />.</para>
227228
/// </remarks>
228229
public static readonly EventId WaitingForVectorIndex = MakeDatabaseId(Id.WaitingForVectorIndex);
230+
231+
/// <summary>
232+
/// EF Core is waiting for search indexes to be ready.
233+
/// </summary>
234+
/// <remarks>
235+
/// <para>This event is in the <see cref="DbLoggerCategory.Database" /> category.</para>
236+
/// <para>This event uses the <see cref="TimeSpanEventData" /> payload when used with a <see cref="DiagnosticSource" />.</para>
237+
/// </remarks>
238+
public static readonly EventId WaitingForSearchIndex = MakeDatabaseId(Id.WaitingForSearchIndex);
229239
}

src/MongoDB.EntityFrameworkCore/Diagnostics/MongoLoggerExtensions.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,6 @@ private static EventDefinition<string, string, string> LogVectorSearchReturnedZe
376376
"'DbContext.Database.WaitForVectorIndexes'.";
377377

378378

379-
380379
public static void WaitingForVectorIndex(
381380
this IDiagnosticsLogger<DbLoggerCategory.Database> diagnostics,
382381
TimeSpan remainingBeforeTimeout)
@@ -424,4 +423,53 @@ private static EventDefinition<string> LogWaitingForVectorIndex(IDiagnosticsLogg
424423
private const string WaitingForVectorIndexString =
425424
"EF Core is waiting for vector indexes to be ready. The time remaining before failing with a timeout is " +
426425
"{secondsRemaining} seconds.";
426+
427+
428+
public static void WaitingForSearchIndex(
429+
this IDiagnosticsLogger<DbLoggerCategory.Database> diagnostics,
430+
TimeSpan remainingBeforeTimeout)
431+
{
432+
var definition = LogWaitingForSearchIndex(diagnostics);
433+
434+
if (diagnostics.ShouldLog(definition))
435+
{
436+
definition.Log(diagnostics, remainingBeforeTimeout.TotalSeconds.ToString(CultureInfo.InvariantCulture));
437+
}
438+
439+
if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
440+
{
441+
var eventData = new TimeSpanEventData(
442+
definition,
443+
(d, p) => ((EventDefinition<string>)d).GenerateMessage(
444+
((TimeSpanEventData)p).TimeSpan.TotalSeconds.ToString(CultureInfo.InvariantCulture)),
445+
remainingBeforeTimeout);
446+
diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
447+
}
448+
}
449+
450+
private static EventDefinition<string> LogWaitingForSearchIndex(IDiagnosticsLogger logger)
451+
{
452+
var definition = ((MongoLoggingDefinitions)logger.Definitions).LogWaitingForSearchIndex;
453+
if (definition == null)
454+
{
455+
definition = NonCapturingLazyInitializer.EnsureInitialized(
456+
ref ((MongoLoggingDefinitions)logger.Definitions).LogWaitingForSearchIndex,
457+
logger,
458+
static logger => new EventDefinition<string>(
459+
logger.Options,
460+
MongoEventId.WaitingForSearchIndex,
461+
LogLevel.Information,
462+
"MongoEventId.WaitingForSearchIndex",
463+
level => LoggerMessage.Define<string>(
464+
level,
465+
MongoEventId.WaitingForSearchIndex,
466+
WaitingForSearchIndexString)));
467+
}
468+
469+
return (EventDefinition<string>)definition;
470+
}
471+
472+
private const string WaitingForSearchIndexString =
473+
"EF Core is waiting for search indexes to be ready. The time remaining before failing with a timeout is " +
474+
"{secondsRemaining} seconds.";
427475
}

src/MongoDB.EntityFrameworkCore/Diagnostics/MongoLoggingDefinitions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,5 @@ internal class MongoLoggingDefinitions : LoggingDefinitions
4747
public EventDefinitionBase? LogVectorSearchReturnedZeroResults;
4848

4949
public EventDefinitionBase? LogWaitingForVectorIndex;
50+
public EventDefinitionBase? LogWaitingForSearchIndex;
5051
}

src/MongoDB.EntityFrameworkCore/Extensions/MongoDatabaseFacadeExtensions.cs

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
using Microsoft.EntityFrameworkCore.Storage;
2222
using MongoDB.Driver;
2323
using MongoDB.EntityFrameworkCore.Metadata;
24+
using MongoDB.EntityFrameworkCore.Metadata.Search;
25+
using MongoDB.EntityFrameworkCore.Metadata.Search.Definitions;
2426
using MongoDB.EntityFrameworkCore.Storage;
2527

2628
// ReSharper disable once CheckNamespace
@@ -34,7 +36,8 @@ public static class MongoDatabaseFacadeExtensions
3436
{
3537
/// <summary>
3638
/// Creates an index in MongoDB based on the EF Core <see cref="IIndex"/> definition. No attempt is made to check that the index
37-
/// does not already exist and can therefore be created. The index may be an Atlas index or a normal MongoDB index.
39+
/// does not already exist and can therefore be created. The index may be a MongoDB search index, MongoDB vector search index,
40+
/// or a normal MongoDB index.
3841
/// </summary>
3942
/// <param name="databaseFacade">The <see cref="DatabaseFacade"/> from the EF Core <see cref="Microsoft.EntityFrameworkCore.DbContext"/>.</param>
4043
/// <param name="index">The <see cref="IIndex"/> definition.</param>
@@ -47,7 +50,8 @@ public static void CreateIndex(this DatabaseFacade databaseFacade, IIndex index)
4750

4851
/// <summary>
4952
/// Creates an index in MongoDB based on the EF Core <see cref="IIndex"/> definition. No attempt is made to check that the index
50-
/// does not already exist and can therefore be created. The index may be an Atlas index or a normal MongoDB index.
53+
/// does not already exist and can therefore be created. The index may be a MongoDB search index, MongoDB vector index, or a
54+
/// normal MongoDB index.
5155
/// </summary>
5256
/// <param name="databaseFacade">The <see cref="DatabaseFacade"/> from the EF Core <see cref="Microsoft.EntityFrameworkCore.DbContext"/>.</param>
5357
/// <param name="index">The <see cref="IIndex"/> definition.</param>
@@ -62,14 +66,14 @@ public static Task CreateIndexAsync(this DatabaseFacade databaseFacade, IIndex i
6266

6367
/// <summary>
6468
/// Creates indexes in the MongoDB database for all <see cref="IIndex"/> definitions in the EF Core model for which there
65-
/// is not already an index in the database. This method only creates regular, non-Atlas indexes.
69+
/// is not already an index in the database. This method only creates regular, non-search and non-vector search indexes.
6670
/// </summary>
6771
/// <param name="databaseFacade">The <see cref="DatabaseFacade"/> from the EF Core <see cref="Microsoft.EntityFrameworkCore.DbContext"/>.</param>
6872
public static void CreateMissingIndexes(this DatabaseFacade databaseFacade)
6973
=> GetDatabaseCreator(databaseFacade).CreateMissingIndexes();
7074

7175
/// <summary>
72-
/// Creates missing Atlas vector indexes in the MongoDB database for all <see cref="IIndex"/> definitions in the EF Core model for
76+
/// Creates missing MongoDB vector indexes in the MongoDB database for all <see cref="IIndex"/> definitions in the EF Core model for
7377
/// which there is not already an index in the database.
7478
/// </summary>
7579
/// <param name="databaseFacade">The <see cref="DatabaseFacade"/> from the EF Core <see cref="Microsoft.EntityFrameworkCore.DbContext"/>.</param>
@@ -78,7 +82,7 @@ public static void CreateMissingVectorIndexes(this DatabaseFacade databaseFacade
7882

7983
/// <summary>
8084
/// Creates indexes in the MongoDB database for all <see cref="IIndex"/> definitions in the EF Core model for which there
81-
/// is not already an index in the database. This method only creates regular, non-Atlas indexes.
85+
/// is not already an index in the database. This method only creates regular, non-search and non-vector search indexes.
8286
/// </summary>
8387
/// <param name="databaseFacade">The <see cref="DatabaseFacade"/> from the EF Core <see cref="Microsoft.EntityFrameworkCore.DbContext"/>.</param>
8488
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel this asynchronous request.</param>
@@ -87,7 +91,7 @@ public static Task CreateMissingIndexesAsync(this DatabaseFacade databaseFacade,
8791
=> GetDatabaseCreator(databaseFacade).CreateMissingIndexesAsync(cancellationToken);
8892

8993
/// <summary>
90-
/// Creates missing Atlas vector indexes in the MongoDB database for all <see cref="IIndex"/> definitions in the EF Core model for
94+
/// Creates missing MongoDB vector indexes in the MongoDB database for all <see cref="IIndex"/> definitions in the EF Core model for
9195
/// which there is not already an index in the database.
9296
/// </summary>
9397
/// <param name="databaseFacade">The <see cref="DatabaseFacade"/> from the EF Core <see cref="Microsoft.EntityFrameworkCore.DbContext"/>.</param>
@@ -118,6 +122,46 @@ public static void WaitForVectorIndexes(this DatabaseFacade databaseFacade, Time
118122
public static Task WaitForVectorIndexesAsync(this DatabaseFacade databaseFacade, TimeSpan? timeout = null, CancellationToken cancellationToken = default)
119123
=> GetDatabaseCreator(databaseFacade).WaitForVectorIndexesAsync(timeout, cancellationToken);
120124

125+
/// <summary>
126+
/// Creates missing MongoDB search indexes in the MongoDB database for all <see cref="IIndex"/> definitions in the EF Core model for
127+
/// which there is not already an index in the database.
128+
/// </summary>
129+
/// <param name="databaseFacade">The <see cref="DatabaseFacade"/> from the EF Core <see cref="Microsoft.EntityFrameworkCore.DbContext"/>.</param>
130+
public static void CreateMissingSearchIndexes(this DatabaseFacade databaseFacade)
131+
=> GetDatabaseCreator(databaseFacade).CreateMissingSearchIndexes();
132+
133+
/// <summary>
134+
/// Creates missing MongoDB search indexes in the MongoDB database for all <see cref="SearchIndexDefinition"/> definitions in
135+
/// the EF Core model for which there is not already an index in the database.
136+
/// </summary>
137+
/// <param name="databaseFacade">The <see cref="DatabaseFacade"/> from the EF Core <see cref="Microsoft.EntityFrameworkCore.DbContext"/>.</param>
138+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel this asynchronous request.</param>
139+
/// <returns>A <see cref="Task"/> to track this async operation.</returns>
140+
public static Task CreateMissingSearchIndexesAsync(this DatabaseFacade databaseFacade, CancellationToken cancellationToken = default)
141+
=> GetDatabaseCreator(databaseFacade).CreateMissingSearchIndexesAsync(cancellationToken);
142+
143+
/// <summary>
144+
/// Blocks until all search indexes in the mapped collections are reporting the 'READY' state.
145+
/// </summary>
146+
/// <param name="databaseFacade">The <see cref="DatabaseFacade"/> from the EF Core <see cref="Microsoft.EntityFrameworkCore.DbContext"/>.</param>
147+
/// <param name="timeout">The minimum amount of time to wait for all indexes to be 'READY' before aborting.
148+
/// The default is 60 seconds. Zero seconds means no timeout.</param>
149+
/// <exception cref="InvalidOperationException">if the timeout expires before all indexes are 'READY'.</exception>
150+
public static void WaitForSearchIndexes(this DatabaseFacade databaseFacade, TimeSpan? timeout = null)
151+
=> GetDatabaseCreator(databaseFacade).WaitForSearchIndexes(timeout);
152+
153+
/// <summary>
154+
/// Blocks until all search indexes in the mapped collections are reporting the 'READY' state.
155+
/// </summary>
156+
/// <param name="databaseFacade">The <see cref="DatabaseFacade"/> from the EF Core <see cref="Microsoft.EntityFrameworkCore.DbContext"/>.</param>
157+
/// <param name="timeout">The minimum amount of time to wait for all indexes to be 'READY' before aborting.
158+
/// The default is 60 seconds. Zero seconds means no timeout.</param>
159+
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel this asynchronous request.</param>
160+
/// <returns>A <see cref="Task"/> to track this async operation.</returns>
161+
/// <exception cref="InvalidOperationException">if the timeout expires before all indexes are 'READY'.</exception>
162+
public static Task WaitForSearchIndexesAsync(this DatabaseFacade databaseFacade, TimeSpan? timeout = null, CancellationToken cancellationToken = default)
163+
=> GetDatabaseCreator(databaseFacade).WaitForSearchIndexesAsync(timeout, cancellationToken);
164+
121165
/// <summary>
122166
/// Ensures that the database for the context exists. If it exists, no action is taken. If it does not
123167
/// exist then the MongoDB database is created using the <see cref="MongoDatabaseCreationOptions"/> to determine what

0 commit comments

Comments
 (0)