Skip to content

Commit 0024b2b

Browse files
authored
Cosmos Full Text Search support (#35868)
* Cosmos Full Text Search support - Adding model building API to configure property as full-text search enabled, as well as setup the index for it, - Adding model validation (e.g. FTS index not matching FTS property), - Adding EF.Functions stubs and translations for FullTextContains, FullTextContainsAll, FullTextContainsAny, FullTextScore and RRF (for hybrid), - Adding logic in SelectExpression to produce ORDER BY RANK when necessary, - Adding validation when attempting to mix with ORDER BY RANK with regular ORDER BY, - Rewrite OFFSET/LIMIT from parameter to constant when ORDER BY RANK is present. - Adding model building support for default language on the container level Also fixed / added support for vector search on owned types (since it shares logic with FTS) and added some tests. outstanding work: - support for FTS Container building using Azure.ResourceManager.CosmosDb (currently blocked on updated package being released) Fixes #35476 Fixes #35851 Fixes #35852 Fixes #35853 Fixes #35867
1 parent e500cda commit 0024b2b

34 files changed

+3024
-119
lines changed

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
<PackageVersion Include="Microsoft.DotNet.Build.Tasks.Templating" Version="$(MicrosoftDotNetBuildTasksTemplatingVersion)" />
3838

3939
<!-- Azure SDK for .NET dependencies -->
40-
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.46.0" />
40+
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.49.0-preview.0" />
4141

4242
<!-- SQL Server dependencies -->
4343
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.0.1" />

src/EFCore.Cosmos/EFCore.Cosmos.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<Description>Azure Cosmos provider for Entity Framework Core.</Description>

src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,55 @@ public static T CoalesceUndefined<T>(
5252
T expression2)
5353
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(CoalesceUndefined)));
5454

55+
/// <summary>
56+
/// Checks if the specified property contains the given keyword using full-text search.
57+
/// </summary>
58+
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
59+
/// <param name="property">The property to search.</param>
60+
/// <param name="keyword">The keyword to search for.</param>
61+
/// <returns><see langword="true" /> if the property contains the keyword; otherwise, <see langword="false" />.</returns>
62+
public static bool FullTextContains(this DbFunctions _, string property, string keyword)
63+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextContains)));
64+
65+
/// <summary>
66+
/// Checks if the specified property contains all the given keywords using full-text search.
67+
/// </summary>
68+
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
69+
/// <param name="property">The property to search.</param>
70+
/// <param name="keywords">The keywords to search for.</param>
71+
/// <returns><see langword="true" /> if the property contains all the keywords; otherwise, <see langword="false" />.</returns>
72+
public static bool FullTextContainsAll(this DbFunctions _, string property, params string[] keywords)
73+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextContainsAll)));
74+
75+
/// <summary>
76+
/// Checks if the specified property contains any of the given keywords using full-text search.
77+
/// </summary>
78+
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
79+
/// <param name="property">The property to search.</param>
80+
/// <param name="keywords">The keywords to search for.</param>
81+
/// <returns><see langword="true" /> if the property contains any of the keywords; otherwise, <see langword="false" />.</returns>
82+
public static bool FullTextContainsAny(this DbFunctions _, string property, params string[] keywords)
83+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextContainsAny)));
84+
85+
/// <summary>
86+
/// Returns the full-text search score for the specified property and keywords.
87+
/// </summary>
88+
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
89+
/// <param name="property">The property to score.</param>
90+
/// <param name="keywords">The keywords to score by.</param>
91+
/// <returns>The full-text search score.</returns>
92+
public static double FullTextScore(this DbFunctions _, string property, params string[] keywords)
93+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(FullTextScore)));
94+
95+
/// <summary>
96+
/// Combines scores provided by two or more specified functions.
97+
/// </summary>
98+
/// <param name="_">The <see cref="DbFunctions" /> instance.</param>
99+
/// <param name="functions">The functions to compute the score for.</param>
100+
/// <returns>The combined score.</returns>
101+
public static double Rrf(this DbFunctions _, params double[] functions)
102+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Rrf)));
103+
55104
/// <summary>
56105
/// Returns the distance between two vectors, using the distance function and data type defined using
57106
/// <see

src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics.CodeAnalysis;
45
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
56

67
// ReSharper disable once CheckNamespace
@@ -883,6 +884,88 @@ public static bool CanSetDefaultTimeToLive(
883884
bool fromDataAnnotation = false)
884885
=> entityTypeBuilder.CanSetAnnotation(CosmosAnnotationNames.DefaultTimeToLive, seconds, fromDataAnnotation);
885886

887+
/// <summary>
888+
/// Configures a default language to use for full-text search at container scope.
889+
/// </summary>
890+
/// <remarks>
891+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
892+
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
893+
/// </remarks>
894+
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
895+
/// <param name="language">The default language.</param>
896+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
897+
public static EntityTypeBuilder HasDefaultFullTextLanguage(
898+
this EntityTypeBuilder entityTypeBuilder,
899+
string? language)
900+
{
901+
entityTypeBuilder.Metadata.SetDefaultFullTextSearchLanguage(language);
902+
903+
return entityTypeBuilder;
904+
}
905+
906+
/// <summary>
907+
/// Configures a default language to use for full-text search at container scope.
908+
/// </summary>
909+
/// <remarks>
910+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
911+
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
912+
/// </remarks>
913+
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
914+
/// <param name="language">The default language.</param>
915+
/// <returns>The same builder instance so that multiple calls can be chained.</returns>
916+
public static EntityTypeBuilder<TEntity> HasDefaultFullTextLanguage<TEntity>(
917+
this EntityTypeBuilder<TEntity> entityTypeBuilder,
918+
string? language)
919+
where TEntity : class
920+
=> (EntityTypeBuilder<TEntity>)HasDefaultFullTextLanguage((EntityTypeBuilder)entityTypeBuilder, language);
921+
922+
/// <summary>
923+
/// Configures a default language to use for full-text search at container scope.
924+
/// </summary>
925+
/// <remarks>
926+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
927+
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
928+
/// </remarks>
929+
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
930+
/// <param name="language">The default language.</param>
931+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
932+
/// <returns>
933+
/// The same builder instance if the configuration was applied,
934+
/// <see langword="null" /> otherwise.
935+
/// </returns>
936+
public static IConventionEntityTypeBuilder? HasDefaultFullTextLanguage(
937+
this IConventionEntityTypeBuilder entityTypeBuilder,
938+
string? language,
939+
bool fromDataAnnotation = false)
940+
{
941+
if (!entityTypeBuilder.CanSetDefaultFullTextLanguage(language, fromDataAnnotation))
942+
{
943+
return null;
944+
}
945+
946+
entityTypeBuilder.Metadata.SetDefaultFullTextSearchLanguage(language, fromDataAnnotation);
947+
948+
return entityTypeBuilder;
949+
}
950+
951+
/// <summary>
952+
/// Returns a value indicating whether the default full-text language can be set
953+
/// from the current configuration source
954+
/// </summary>
955+
/// <remarks>
956+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
957+
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
958+
/// </remarks>
959+
/// <param name="entityTypeBuilder">The builder for the entity type being configured.</param>
960+
/// <param name="language">The default language.</param>
961+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
962+
/// <returns><see langword="true" /> if the configuration can be applied.</returns>
963+
public static bool CanSetDefaultFullTextLanguage(
964+
this IConventionEntityTypeBuilder entityTypeBuilder,
965+
string? language,
966+
bool fromDataAnnotation = false)
967+
=> entityTypeBuilder.CanSetAnnotation(CosmosAnnotationNames.DefaultFullTextSearchLanguage, language, fromDataAnnotation);
968+
886969
/// <summary>
887970
/// Configures the manual provisioned throughput offering.
888971
/// </summary>

src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics.CodeAnalysis;
45
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
56

67
// ReSharper disable once CheckNamespace
@@ -586,4 +587,48 @@ public static void SetThroughput(this IMutableEntityType entityType, int? throug
586587
public static ConfigurationSource? GetThroughputConfigurationSource(this IConventionEntityType entityType)
587588
=> entityType.FindAnnotation(CosmosAnnotationNames.Throughput)
588589
?.GetConfigurationSource();
590+
591+
/// <summary>
592+
/// Returns the default language for the full-text search at container scope.
593+
/// </summary>
594+
/// <param name="entityType">The entity type.</param>
595+
/// <returns>The default language for the full-text search.</returns>
596+
public static string? GetDefaultFullTextSearchLanguage(this IReadOnlyEntityType entityType)
597+
=> entityType.BaseType != null
598+
? entityType.GetRootType().GetDefaultFullTextSearchLanguage()
599+
: (string?)entityType[CosmosAnnotationNames.DefaultFullTextSearchLanguage];
600+
601+
/// <summary>
602+
/// Sets the default language for the full-text search at container scope.
603+
/// </summary>
604+
/// <param name="entityType">The entity type.</param>
605+
/// <param name="language">The default language for the full-text search.</param>
606+
public static void SetDefaultFullTextSearchLanguage(this IMutableEntityType entityType, string? language)
607+
=> entityType.SetOrRemoveAnnotation(
608+
CosmosAnnotationNames.DefaultFullTextSearchLanguage,
609+
language);
610+
611+
/// <summary>
612+
/// Sets the default language for the full-text search at container scope.
613+
/// </summary>
614+
/// <param name="entityType">The entity type.</param>
615+
/// <param name="language">The default language for the full-text search.</param>
616+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
617+
public static string? SetDefaultFullTextSearchLanguage(
618+
this IConventionEntityType entityType,
619+
string? language,
620+
bool fromDataAnnotation = false)
621+
=> (string?)entityType.SetOrRemoveAnnotation(
622+
CosmosAnnotationNames.DefaultFullTextSearchLanguage,
623+
language,
624+
fromDataAnnotation)?.Value;
625+
626+
/// <summary>
627+
/// Gets the <see cref="ConfigurationSource" /> for the default full-text search language at container scope.
628+
/// </summary>
629+
/// <param name="entityType">The entity type to find configuration source for.</param>
630+
/// <returns>The <see cref="ConfigurationSource" /> for the default full-text search language.</returns>
631+
public static ConfigurationSource? GetDefaultFullTextSearchLanguageConfigurationSource(this IConventionEntityType entityType)
632+
=> entityType.FindAnnotation(CosmosAnnotationNames.DefaultFullTextSearchLanguage)
633+
?.GetConfigurationSource();
589634
}

src/EFCore.Cosmos/Extensions/CosmosIndexBuilderExtensions.cs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ namespace Microsoft.EntityFrameworkCore;
1414
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
1515
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
1616
/// </remarks>
17-
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
1817
public static class CosmosIndexBuilderExtensions
1918
{
2019
/// <summary>
@@ -28,6 +27,7 @@ public static class CosmosIndexBuilderExtensions
2827
/// <param name="indexBuilder">The builder for the index being configured.</param>
2928
/// <param name="indexType">The type of vector index to create.</param>
3029
/// <returns>A builder to further configure the index.</returns>
30+
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
3131
public static IndexBuilder ForVectors(this IndexBuilder indexBuilder, VectorIndexType? indexType)
3232
{
3333
indexBuilder.Metadata.SetVectorIndexType(indexType);
@@ -46,6 +46,7 @@ public static IndexBuilder ForVectors(this IndexBuilder indexBuilder, VectorInde
4646
/// <param name="indexBuilder">The builder for the index being configured.</param>
4747
/// <param name="indexType">The type of vector index to create.</param>
4848
/// <returns>A builder to further configure the index.</returns>
49+
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
4950
public static IndexBuilder<TEntity> ForVectors<TEntity>(
5051
this IndexBuilder<TEntity> indexBuilder,
5152
VectorIndexType? indexType)
@@ -66,6 +67,7 @@ public static IndexBuilder<TEntity> ForVectors<TEntity>(
6667
/// The same builder instance if the configuration was applied,
6768
/// <see langword="null" /> otherwise.
6869
/// </returns>
70+
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
6971
public static IConventionIndexBuilder? ForVectors(
7072
this IConventionIndexBuilder indexBuilder,
7173
VectorIndexType? indexType,
@@ -91,9 +93,90 @@ public static IndexBuilder<TEntity> ForVectors<TEntity>(
9193
/// <param name="indexType">The index type to use.</param>
9294
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
9395
/// <returns><see langword="true" /> if the index can be configured for vectors.</returns>
96+
[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
9497
public static bool CanSetVectorIndexType(
9598
this IConventionIndexBuilder indexBuilder,
9699
VectorIndexType? indexType,
97100
bool fromDataAnnotation = false)
98101
=> indexBuilder.CanSetAnnotation(CosmosAnnotationNames.VectorIndexType, indexType, fromDataAnnotation);
102+
103+
/// <summary>
104+
/// Configures the index as a full-text index.
105+
/// See <see href="https://learn.microsoft.com/azure/cosmos-db/gen-ai/full-text-search">Full-text search in Azure Cosmos DB for NoSQL</see> for more information.
106+
/// </summary>
107+
/// <remarks>
108+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
109+
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
110+
/// </remarks>
111+
/// <param name="indexBuilder">The builder for the index being configured.</param>
112+
/// <param name="value">The value indicating whether the index is configured for Full-text search.</param>
113+
/// <returns>A builder to further configure the index.</returns>
114+
public static IndexBuilder IsFullTextIndex(this IndexBuilder indexBuilder, bool? value = true)
115+
{
116+
indexBuilder.Metadata.SetIsFullTextIndex(value);
117+
118+
return indexBuilder;
119+
}
120+
121+
/// <summary>
122+
/// Configures the index as a full-text index.
123+
/// See <see href="https://learn.microsoft.com/azure/cosmos-db/gen-ai/full-text-search">Full-text search in Azure Cosmos DB for NoSQL</see> for more information.
124+
/// </summary>
125+
/// <remarks>
126+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
127+
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
128+
/// </remarks>
129+
/// <param name="indexBuilder">The builder for the index being configured.</param>
130+
/// <param name="value">The value indicating whether the index is configured for Full-text search.</param>
131+
/// <returns>A builder to further configure the index.</returns>
132+
public static IndexBuilder<TEntity> IsFullTextIndex<TEntity>(
133+
this IndexBuilder<TEntity> indexBuilder,
134+
bool? value = true)
135+
=> (IndexBuilder<TEntity>)IsFullTextIndex((IndexBuilder)indexBuilder, value);
136+
137+
/// <summary>
138+
/// Configures the index as a full-text index.
139+
/// See <see href="https://learn.microsoft.com/azure/cosmos-db/gen-ai/full-text-search">Full-text search in Azure Cosmos DB for NoSQL</see> for more information.
140+
/// </summary>
141+
/// <remarks>
142+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
143+
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
144+
/// </remarks>
145+
/// <param name="indexBuilder">The builder for the index being configured.</param>
146+
/// <param name="value">The value indicating whether the index is configured for Full-text search.</param>
147+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
148+
/// <returns>
149+
/// The same builder instance if the configuration was applied,
150+
/// <see langword="null" /> otherwise.
151+
/// </returns>
152+
public static IConventionIndexBuilder? IsFullTextIndex(
153+
this IConventionIndexBuilder indexBuilder,
154+
bool? value,
155+
bool fromDataAnnotation = false)
156+
{
157+
if (indexBuilder.CanSetIsFullTextIndex(fromDataAnnotation))
158+
{
159+
indexBuilder.Metadata.SetIsFullTextIndex(value, fromDataAnnotation);
160+
return indexBuilder;
161+
}
162+
163+
return null;
164+
}
165+
166+
/// <summary>
167+
/// Returns a value indicating whether the index can be configured as a full-text index.
168+
/// </summary>
169+
/// <remarks>
170+
/// See <see href="https://aka.ms/efcore-docs-modeling">Modeling entity types and relationships</see>, and
171+
/// <see href="https://aka.ms/efcore-docs-cosmos">Accessing Azure Cosmos DB with EF Core</see> for more information and examples.
172+
/// </remarks>
173+
/// <param name="indexBuilder">The builder for the index being configured.</param>
174+
/// <param name="value">The value indicating whether the index is configured for Full-text search.</param>
175+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
176+
/// <returns><see langword="true" /> if the index can be configured as a Full-text index.</returns>
177+
public static bool CanSetIsFullTextIndex(
178+
this IConventionIndexBuilder indexBuilder,
179+
bool? value,
180+
bool fromDataAnnotation = false)
181+
=> indexBuilder.CanSetAnnotation(CosmosAnnotationNames.FullTextIndex, value, fromDataAnnotation);
99182
}

0 commit comments

Comments
 (0)