Skip to content

Commit b554e29

Browse files
committed
Add support for default language at the container level. This is in preparation for Cosmos supporting more languages than just en-US.
1 parent 65a57b8 commit b554e29

15 files changed

+467
-70
lines changed

Diff for: src/EFCore.Cosmos/EFCore.Cosmos.csproj

+1-1
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>

Diff for: src/EFCore.Cosmos/Extensions/CosmosEntityTypeBuilderExtensions.cs

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

Diff for: src/EFCore.Cosmos/Extensions/CosmosEntityTypeExtensions.cs

+49
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,52 @@ 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+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
597+
public static string? GetDefaultFullTextSearchLanguage(this IReadOnlyEntityType entityType)
598+
=> entityType.BaseType != null
599+
? entityType.GetRootType().GetDefaultFullTextSearchLanguage()
600+
: (string?)entityType[CosmosAnnotationNames.FullTextSearchDefaultLanguage];
601+
602+
/// <summary>
603+
/// Sets the default language for the full-text search at container scope.
604+
/// </summary>
605+
/// <param name="entityType">The entity type.</param>
606+
/// <param name="language">The default language for the full-text search.</param>
607+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
608+
public static void SetDefaultFullTextSearchLanguage(this IMutableEntityType entityType, string? language)
609+
=> entityType.SetOrRemoveAnnotation(
610+
CosmosAnnotationNames.FullTextSearchDefaultLanguage,
611+
language);
612+
613+
/// <summary>
614+
/// Sets the default language for the full-text search at container scope.
615+
/// </summary>
616+
/// <param name="entityType">The entity type.</param>
617+
/// <param name="language">The default language for the full-text search.</param>
618+
/// <param name="fromDataAnnotation">Indicates whether the configuration was specified using a data annotation.</param>
619+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
620+
public static string? SetDefaultFullTextSearchLanguage(
621+
this IConventionEntityType entityType,
622+
string? language,
623+
bool fromDataAnnotation = false)
624+
=> (string?)entityType.SetOrRemoveAnnotation(
625+
CosmosAnnotationNames.FullTextSearchDefaultLanguage,
626+
language,
627+
fromDataAnnotation)?.Value;
628+
629+
/// <summary>
630+
/// Gets the <see cref="ConfigurationSource" /> for the default full-text search language at container scope.
631+
/// </summary>
632+
/// <param name="entityType">The entity type to find configuration source for.</param>
633+
/// <returns>The <see cref="ConfigurationSource" /> for the default full-text search language.</returns>
634+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
635+
public static ConfigurationSource? GetDefaultFullTextSearchLanguageConfigurationSource(this IConventionEntityType entityType)
636+
=> entityType.FindAnnotation(CosmosAnnotationNames.DefaultTimeToLive)
637+
?.GetConfigurationSource();
589638
}

Diff for: src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public static void SetVectorType(this IMutableProperty property, CosmosVectorTyp
140140
var annotation = property.FindAnnotation(CosmosAnnotationNames.FullTextSearchLanguage);
141141

142142
return annotation != null
143-
? (string?)annotation.Value ?? "en-US"
143+
? (string?)annotation.Value ?? (property.DeclaringType as IReadOnlyEntityType)?.GetDefaultFullTextSearchLanguage() ?? "en-US"
144144
: null;
145145
}
146146

Diff for: src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs

+22
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ protected virtual void ValidateSharedContainerCompatibility(
166166
int? analyticalTtl = null;
167167
int? defaultTtl = null;
168168
ThroughputProperties? throughput = null;
169+
string? defaultFullTextSearchLanguage = null;
169170
IEntityType? firstEntityType = null;
170171
bool? isDiscriminatorMappingComplete = null;
171172

@@ -330,6 +331,27 @@ protected virtual void ValidateSharedContainerCompatibility(
330331
CosmosStrings.ThroughputTypeMismatch(manualType.DisplayName(), autoscaleType.DisplayName(), container));
331332
}
332333
}
334+
335+
var currentFullTextSearchDefaultLanguage = entityType.GetDefaultFullTextSearchLanguage();
336+
if (currentFullTextSearchDefaultLanguage != null)
337+
{
338+
if (defaultFullTextSearchLanguage == null)
339+
{
340+
defaultFullTextSearchLanguage = currentFullTextSearchDefaultLanguage;
341+
}
342+
else if (defaultFullTextSearchLanguage != currentFullTextSearchDefaultLanguage)
343+
{
344+
var conflictingEntityType = mappedTypes.First(et => et.GetDefaultFullTextSearchLanguage() != null);
345+
346+
throw new InvalidOperationException(
347+
CosmosStrings.FullTextSearchDefaultLanguageMismatch(
348+
defaultFullTextSearchLanguage,
349+
conflictingEntityType.DisplayName(),
350+
entityType.DisplayName(),
351+
currentFullTextSearchDefaultLanguage,
352+
container));
353+
}
354+
}
333355
}
334356
}
335357

Diff for: src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationNames.cs

+9
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ public static class CosmosAnnotationNames
5454
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
5555
public const string FullTextIndex = Prefix + "FullTextIndex";
5656

57+
/// <summary>
58+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
59+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
60+
/// any release. You should only use it directly in your code with extreme caution and knowing that
61+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
62+
/// </summary>
63+
[Experimental(EFDiagnostics.CosmosFullTextSearchExperimental)]
64+
public const string FullTextSearchDefaultLanguage = Prefix + "FullTextSearchDefaultLanguage";
65+
5766
/// <summary>
5867
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
5968
/// the same compatibility standards as public APIs. It may be changed or removed without notice in

Diff for: src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs

+8-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: src/EFCore.Cosmos/Properties/CosmosStrings.resx

+3-3
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,12 @@
177177
<data name="FullTextIndexOnNonFullTextProperty" xml:space="preserve">
178178
<value>A full-text index is defined for `{entityType}.{property}`, but this property has not been configured for full-text search. Use '{isFullText}' method in 'OnModelCreating' to configure the property for full-text search.</value>
179179
</data>
180-
<data name="FullTextPropertyWithoutFullTextIndex" xml:space="preserve">
181-
<value>A property '{entityType}.{property}' was configured for full-text search, but an associated full-text index was not defined. Define an index for the property and configure it as a full-text index using the '{isFullText}' method.</value>
182-
</data>
183180
<data name="FullTextSearchConfiguredForUnsupportedPropertyType" xml:space="preserve">
184181
<value>Property '{entityType}.{property}' was configured for full-text search, but has type '{clrType}'; only string properties can be configured for full-text search.</value>
185182
</data>
183+
<data name="FullTextSearchDefaultLanguageMismatch" xml:space="preserve">
184+
<value>The default full-text search language was configured to '{defaultLanguage1}' on '{entityType1}', but on '{entityType2}' it was configured to '{defaultLanguage2}'. All entity types mapped to the same container '{container}' must be configured with the same default full-text search language.</value>
185+
</data>
186186
<data name="HasShadowIdOnNonRoot" xml:space="preserve">
187187
<value>'HasShadowId' was called on a non-root entity type '{entityType}'. JSON 'id' configuration can only be made on the document root.</value>
188188
</data>

Diff for: src/EFCore.Cosmos/Storage/Internal/ContainerProperties.cs

+1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ public readonly record struct ContainerProperties(
1919
ThroughputProperties? Throughput,
2020
IReadOnlyList<IIndex> Indexes,
2121
IReadOnlyList<(IProperty Property, CosmosVectorType VectorType)> Vectors,
22+
string DefaultFullTextLanguage,
2223
IReadOnlyList<(IProperty Property, string Language)> FullTextProperties);

Diff for: src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs

+5-12
Original file line numberDiff line numberDiff line change
@@ -277,19 +277,9 @@ private static async Task<bool> CreateContainerIfNotExistsOnceAsync(
277277
}
278278
}
279279

280-
var fullTextIndexProperties = parameters.Indexes.Where(x => x.IsFullTextIndex() == true).Select(x => x.Properties[0]).ToList();
281280
var fullTextPaths = new Collection<FullTextPath>();
282281
foreach (var fullTextProperty in parameters.FullTextProperties)
283282
{
284-
if (!fullTextIndexProperties.Contains(fullTextProperty.Property))
285-
{
286-
throw new InvalidOperationException(
287-
CosmosStrings.FullTextPropertyWithoutFullTextIndex(
288-
fullTextProperty.Property.DeclaringType.DisplayName(),
289-
fullTextProperty.Property.Name,
290-
nameof(CosmosIndexBuilderExtensions.IsFullTextIndex)));
291-
}
292-
293283
if (fullTextProperty.Property.ClrType != typeof(string))
294284
{
295285
throw new InvalidOperationException(
@@ -343,8 +333,11 @@ private static async Task<bool> CreateContainerIfNotExistsOnceAsync(
343333

344334
if (fullTextPaths.Count != 0)
345335
{
346-
// TODO: see issue #35851
347-
containerProperties.FullTextPolicy = new FullTextPolicy { DefaultLanguage = "en-US", FullTextPaths = fullTextPaths };
336+
containerProperties.FullTextPolicy = new FullTextPolicy
337+
{
338+
DefaultLanguage = parameters.DefaultFullTextLanguage,
339+
FullTextPaths = fullTextPaths
340+
};
348341
}
349342

350343
var response = await wrapper.Client.GetDatabase(wrapper._databaseId).CreateContainerIfNotExistsAsync(

Diff for: src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs

+3
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ private static IEnumerable<ContainerProperties> GetContainersToCreate(IModel mod
126126
ThroughputProperties? throughput = null;
127127
var indexes = new List<IIndex>();
128128
var vectors = new List<(IProperty Property, CosmosVectorType VectorType)>();
129+
string? fullTextDefaultLanguage = null;
129130
var fullTextProperties = new List<(IProperty Property, string Language)>();
130131

131132
foreach (var entityType in mappedTypes)
@@ -138,6 +139,7 @@ private static IEnumerable<ContainerProperties> GetContainersToCreate(IModel mod
138139
analyticalTtl ??= entityType.GetAnalyticalStoreTimeToLive();
139140
defaultTtl ??= entityType.GetDefaultTimeToLive();
140141
throughput ??= entityType.GetThroughput();
142+
fullTextDefaultLanguage ??= entityType.GetDefaultFullTextSearchLanguage();
141143

142144
ProcessEntityType(entityType, indexes, vectors, fullTextProperties);
143145
}
@@ -150,6 +152,7 @@ private static IEnumerable<ContainerProperties> GetContainersToCreate(IModel mod
150152
throughput,
151153
indexes,
152154
vectors,
155+
fullTextDefaultLanguage ?? "en-US",
153156
fullTextProperties);
154157
}
155158

0 commit comments

Comments
 (0)