diff --git a/src/EFCore.Analyzers/EFDiagnostics.cs b/src/EFCore.Analyzers/EFDiagnostics.cs
index acedc129899..c3021424089 100644
--- a/src/EFCore.Analyzers/EFDiagnostics.cs
+++ b/src/EFCore.Analyzers/EFDiagnostics.cs
@@ -18,4 +18,5 @@ public static class EFDiagnostics
public const string PrecompiledQueryExperimental = "EF9100";
public const string MetricsExperimental = "EF9101";
public const string PagingExperimental = "EF9102";
+ public const string CosmosVectorSearchExperimental = "EF9103";
}
diff --git a/src/EFCore.Cosmos/EFCore.Cosmos.csproj b/src/EFCore.Cosmos/EFCore.Cosmos.csproj
index 133479d9c1d..4b064e25562 100644
--- a/src/EFCore.Cosmos/EFCore.Cosmos.csproj
+++ b/src/EFCore.Cosmos/EFCore.Cosmos.csproj
@@ -11,6 +11,7 @@
true$(NoWarn);EF9101$(NoWarn);EF9102
+ $(NoWarn);EF9103
diff --git a/src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs
index 94a5b2d50af..f562fa59c36 100644
--- a/src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs
+++ b/src/EFCore.Cosmos/Extensions/CosmosDbFunctionsExtensions.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
+
namespace Microsoft.EntityFrameworkCore.Cosmos.Extensions;
///
@@ -47,4 +49,145 @@ public static T CoalesceUndefined(
T expression1,
T expression2)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(CoalesceUndefined)));
+
+ ///
+ /// Returns the distance between two vectors, using the distance function and data type defined using
+ /// .
+ ///
+ /// The instance.
+ /// The first vector.
+ /// The second vector.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static double VectorDistance(this DbFunctions _, ReadOnlyMemory vector1, ReadOnlyMemory vector2)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(VectorDistance)));
+
+ ///
+ /// Returns the distance between two vectors, given a distance function (aka similarity measure).
+ ///
+ /// The instance.
+ /// The first vector.
+ /// The second vector.
+ /// A specifying how the computed value is used in an ORDER BY
+ /// expression. If , then brute force is used, otherwise any index defined on the vector
+ /// property is leveraged.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static double VectorDistance(
+ this DbFunctions _,
+ ReadOnlyMemory vector1,
+ ReadOnlyMemory vector2,
+ [NotParameterized] bool useBruteForce)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(VectorDistance)));
+
+ ///
+ /// Returns the distance between two vectors, given a distance function (aka similarity measure).
+ ///
+ /// The instance.
+ /// The first vector.
+ /// The second vector.
+ /// The distance function to use.
+ /// A specifying how the computed value is used in an ORDER BY
+ /// expression. If , then brute force is used, otherwise any index defined on the vector
+ /// property is leveraged.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static double VectorDistance(
+ this DbFunctions _,
+ ReadOnlyMemory vector1,
+ ReadOnlyMemory vector2,
+ [NotParameterized] bool useBruteForce,
+ [NotParameterized] DistanceFunction distanceFunction)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(VectorDistance)));
+
+ ///
+ /// Returns the distance between two vectors, using the distance function and data type defined using
+ /// .
+ ///
+ /// The instance.
+ /// The first vector.
+ /// The second vector.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static double VectorDistance(this DbFunctions _, ReadOnlyMemory vector1, ReadOnlyMemory vector2)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(VectorDistance)));
+
+ ///
+ /// Returns the distance between two vectors, given a distance function (aka similarity measure).
+ ///
+ /// The instance.
+ /// The first vector.
+ /// The second vector.
+ /// A specifying how the computed value is used in an ORDER BY
+ /// expression. If , then brute force is used, otherwise any index defined on the vector
+ /// property is leveraged.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static double VectorDistance(
+ this DbFunctions _,
+ ReadOnlyMemory vector1,
+ ReadOnlyMemory vector2,
+ [NotParameterized] bool useBruteForce)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(VectorDistance)));
+
+ ///
+ /// Returns the distance between two vectors, given a distance function (aka similarity measure).
+ ///
+ /// The instance.
+ /// The first vector.
+ /// The second vector.
+ /// The distance function to use.
+ /// A specifying how the computed value is used in an ORDER BY
+ /// expression. If , then brute force is used, otherwise any index defined on the vector
+ /// property is leveraged.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static double VectorDistance(
+ this DbFunctions _,
+ ReadOnlyMemory vector1,
+ ReadOnlyMemory vector2,
+ [NotParameterized] bool useBruteForce,
+ [NotParameterized] DistanceFunction distanceFunction)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(VectorDistance)));
+
+ ///
+ /// Returns the distance between two vectors, using the distance function and data type defined using
+ /// .
+ ///
+ /// The instance.
+ /// The first vector.
+ /// The second vector.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static double VectorDistance(this DbFunctions _, ReadOnlyMemory vector1, ReadOnlyMemory vector2)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(VectorDistance)));
+
+ ///
+ /// Returns the distance between two vectors, given a distance function (aka similarity measure).
+ ///
+ /// The instance.
+ /// The first vector.
+ /// The second vector.
+ /// A specifying how the computed value is used in an ORDER BY
+ /// expression. If , then brute force is used, otherwise any index defined on the vector
+ /// property is leveraged.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static double VectorDistance(
+ this DbFunctions _,
+ ReadOnlyMemory vector1,
+ ReadOnlyMemory vector2,
+ [NotParameterized] bool useBruteForce)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(VectorDistance)));
+
+ ///
+ /// Returns the distance between two vectors, given a distance function (aka similarity measure).
+ ///
+ /// The instance.
+ /// The first vector.
+ /// The second vector.
+ /// The distance function to use.
+ /// A specifying how the computed value is used in an ORDER BY
+ /// expression. If , then brute force is used, otherwise any index defined on the vector
+ /// property is leveraged.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static double VectorDistance(
+ this DbFunctions _,
+ ReadOnlyMemory vector1,
+ ReadOnlyMemory vector2,
+ [NotParameterized] bool useBruteForce,
+ [NotParameterized] DistanceFunction distanceFunction)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(VectorDistance)));
}
diff --git a/src/EFCore.Cosmos/Extensions/CosmosIndexBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosIndexBuilderExtensions.cs
new file mode 100644
index 00000000000..ed0a40b8ce7
--- /dev/null
+++ b/src/EFCore.Cosmos/Extensions/CosmosIndexBuilderExtensions.cs
@@ -0,0 +1,99 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.EntityFrameworkCore;
+
+///
+/// Azure Cosmos DB-specific extension methods for .
+///
+///
+/// See Modeling entity types and relationships, and
+/// Accessing Azure Cosmos DB with EF Core for more information and examples.
+///
+[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+public static class CosmosIndexBuilderExtensions
+{
+ ///
+ /// Configures the index as a vector index with the given vector index type, such as "flat", "diskANN", or "quantizedFlat".
+ /// See Vector Search in Azure Cosmos DB for NoSQL for more information.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing Azure Cosmos DB with EF Core for more information and examples.
+ ///
+ /// The builder for the index being configured.
+ /// The type of vector index to create.
+ /// A builder to further configure the index.
+ public static IndexBuilder ForVectors(this IndexBuilder indexBuilder, VectorIndexType? indexType)
+ {
+ indexBuilder.Metadata.SetVectorIndexType(indexType);
+
+ return indexBuilder;
+ }
+
+ ///
+ /// Configures whether the index as a vector index with the given vector index type, such as "flat", "diskANN", or "quantizedFlat".
+ /// See Vector Search in Azure Cosmos DB for NoSQL for more information.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing Azure Cosmos DB with EF Core for more information and examples.
+ ///
+ /// The builder for the index being configured.
+ /// The type of vector index to create.
+ /// A builder to further configure the index.
+ public static IndexBuilder ForVectors(
+ this IndexBuilder indexBuilder,
+ VectorIndexType? indexType)
+ => (IndexBuilder)ForVectors((IndexBuilder)indexBuilder, indexType);
+
+ ///
+ /// Configures whether the index as a vector index with the given vector index type, such as "flat", "diskANN", or "quantizedFlat".
+ /// See Vector Search in Azure Cosmos DB for NoSQL for more information.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing Azure Cosmos DB with EF Core for more information and examples.
+ ///
+ /// The builder for the index being configured.
+ /// The type of vector index to create.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied,
+ /// otherwise.
+ ///
+ public static IConventionIndexBuilder? ForVectors(
+ this IConventionIndexBuilder indexBuilder,
+ VectorIndexType? indexType,
+ bool fromDataAnnotation = false)
+ {
+ if (indexBuilder.CanSetVectorIndexType(indexType, fromDataAnnotation))
+ {
+ indexBuilder.Metadata.SetVectorIndexType(indexType, fromDataAnnotation);
+ return indexBuilder;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Returns a value indicating whether the vector index can be configured for vectors.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing Azure Cosmos DB with EF Core for more information and examples.
+ ///
+ /// The builder for the index being configured.
+ /// The index type to use.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the index can be configured for vectors.
+ public static bool CanSetVectorIndexType(
+ this IConventionIndexBuilder indexBuilder,
+ VectorIndexType? indexType,
+ bool fromDataAnnotation = false)
+ => indexBuilder.CanSetAnnotation(CosmosAnnotationNames.VectorIndexType, indexType, fromDataAnnotation);
+}
diff --git a/src/EFCore.Cosmos/Extensions/CosmosIndexExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosIndexExtensions.cs
new file mode 100644
index 00000000000..9692a128068
--- /dev/null
+++ b/src/EFCore.Cosmos/Extensions/CosmosIndexExtensions.cs
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.EntityFrameworkCore;
+
+///
+/// Index extension methods for Azure Cosmos DB-specific metadata.
+///
+///
+/// See Modeling entity types and relationships, and
+/// Accessing Azure Cosmos DB with EF Core for more information and examples.
+///
+[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+public static class CosmosIndexExtensions
+{
+ ///
+ /// Returns the vector index type to use, such as "flat", "diskANN", or "quantizedFlat".
+ /// See Vector Search in Azure Cosmos DB for NoSQL for more information.
+ ///
+ /// The index.
+ /// The index type to use, or if none is set.
+ public static VectorIndexType? GetVectorIndexType(this IReadOnlyIndex index)
+ => (index is RuntimeIndex)
+ ? throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData)
+ : (VectorIndexType?)index[CosmosAnnotationNames.VectorIndexType];
+
+ ///
+ /// Sets the vector index type to use, such as "flat", "diskANN", or "quantizedFlat".
+ /// See Vector Search in Azure Cosmos DB for NoSQL for more information.
+ ///
+ /// The index.
+ /// The index type to use.
+ public static void SetVectorIndexType(this IMutableIndex index, VectorIndexType? indexType)
+ => index.SetAnnotation(CosmosAnnotationNames.VectorIndexType, indexType);
+
+ ///
+ /// Sets the vector index type to use, such as "flat", "diskANN", or "quantizedFlat".
+ /// See Vector Search in Azure Cosmos DB for NoSQL for more information.
+ ///
+ /// The index type to use.
+ /// The index.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The configured value.
+ public static string? SetVectorIndexType(
+ this IConventionIndex index,
+ VectorIndexType? indexType,
+ bool fromDataAnnotation = false)
+ => (string?)index.SetAnnotation(
+ CosmosAnnotationNames.VectorIndexType,
+ indexType,
+ fromDataAnnotation)?.Value;
+
+ ///
+ /// Returns the for whether the .
+ ///
+ /// The property.
+ /// The for whether the index is clustered.
+ public static ConfigurationSource? GetVectorIndexTypeConfigurationSource(this IConventionIndex property)
+ => property.FindAnnotation(CosmosAnnotationNames.VectorIndexType)?.GetConfigurationSource();
+}
diff --git a/src/EFCore.Cosmos/Extensions/CosmosPropertyBuilderExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosPropertyBuilderExtensions.cs
index 61da371ef3a..3d8618e3d7f 100644
--- a/src/EFCore.Cosmos/Extensions/CosmosPropertyBuilderExtensions.cs
+++ b/src/EFCore.Cosmos/Extensions/CosmosPropertyBuilderExtensions.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
// ReSharper disable once CheckNamespace
@@ -104,6 +105,105 @@ public static bool CanSetJsonProperty(
bool fromDataAnnotation = false)
=> propertyBuilder.CanSetAnnotation(CosmosAnnotationNames.PropertyName, name, fromDataAnnotation);
+ ///
+ /// Configures the property as a vector for Azure Cosmos DB.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing Azure Cosmos DB with EF Core for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The distance function for a vector comparisons.
+ /// The number of dimensions in the vector.
+ /// The same builder instance so that multiple calls can be chained.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static PropertyBuilder IsVector(
+ this PropertyBuilder propertyBuilder,
+ DistanceFunction distanceFunction,
+ int dimensions)
+ {
+ propertyBuilder.Metadata.SetVectorType(CreateVectorType(distanceFunction, dimensions));
+ return propertyBuilder;
+ }
+
+ ///
+ /// Configures the property as a vector for Azure Cosmos DB.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing Azure Cosmos DB with EF Core for more information and examples.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing Azure Cosmos DB with EF Core for more information and examples.
+ ///
+ /// The type of the property being configured.
+ /// The builder for the property being configured.
+ /// The distance function for a vector comparisons.
+ /// The number of dimensions in the vector.
+ /// The same builder instance so that multiple calls can be chained.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static PropertyBuilder IsVector(
+ this PropertyBuilder propertyBuilder,
+ DistanceFunction distanceFunction,
+ int dimensions)
+ => (PropertyBuilder)IsVector((PropertyBuilder)propertyBuilder, distanceFunction, dimensions);
+
+ ///
+ /// Configures the property as a vector for Azure Cosmos DB.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing Azure Cosmos DB with EF Core for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The distance function for a vector comparisons.
+ /// The number of dimensions in the vector.
+ /// Indicates whether the configuration was specified using a data annotation.
+ ///
+ /// The same builder instance if the configuration was applied,
+ /// otherwise.
+ ///
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static IConventionPropertyBuilder? IsVector(
+ this IConventionPropertyBuilder propertyBuilder,
+ DistanceFunction distanceFunction,
+ int dimensions,
+ bool fromDataAnnotation = false)
+ {
+ if (!propertyBuilder.CanSetIsVector(distanceFunction, dimensions, fromDataAnnotation))
+ {
+ return null;
+ }
+
+ propertyBuilder.Metadata.SetVectorType(CreateVectorType(distanceFunction, dimensions), fromDataAnnotation);
+
+ return propertyBuilder;
+ }
+
+ ///
+ /// Returns a value indicating whether the vector type can be set.
+ ///
+ ///
+ /// See Modeling entity types and relationships, and
+ /// Accessing Azure Cosmos DB with EF Core for more information and examples.
+ ///
+ /// The builder for the property being configured.
+ /// The distance function for a vector comparisons.
+ /// The number of dimensions in the vector.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// if the vector type can be set.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static bool CanSetIsVector(
+ this IConventionPropertyBuilder propertyBuilder,
+ DistanceFunction distanceFunction,
+ int dimensions,
+ bool fromDataAnnotation = false)
+ => propertyBuilder.CanSetAnnotation(
+ CosmosAnnotationNames.VectorType,
+ CreateVectorType(distanceFunction, dimensions),
+ fromDataAnnotation);
+
///
/// Configures this property to be the etag concurrency token.
///
@@ -136,4 +236,11 @@ public static PropertyBuilder IsETagConcurrency(this PropertyBuilder propertyBui
public static PropertyBuilder IsETagConcurrency(
this PropertyBuilder propertyBuilder)
=> (PropertyBuilder)IsETagConcurrency((PropertyBuilder)propertyBuilder);
+
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ private static CosmosVectorType CreateVectorType(DistanceFunction distanceFunction, int dimensions)
+ => Enum.IsDefined(distanceFunction)
+ ? new CosmosVectorType(distanceFunction, dimensions)
+ : throw new ArgumentException(
+ CoreStrings.InvalidEnumValue(distanceFunction, nameof(distanceFunction), typeof(DistanceFunction)));
}
diff --git a/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs
index a397598c506..c4e0cb43466 100644
--- a/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs
+++ b/src/EFCore.Cosmos/Extensions/CosmosPropertyExtensions.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
// ReSharper disable once CheckNamespace
@@ -73,12 +74,55 @@ public static void SetJsonPropertyName(this IMutableProperty property, string? n
fromDataAnnotation)?.Value;
///
- /// Gets the the property name that the property is mapped to when targeting Cosmos.
+ /// Gets the for the property name that the property is mapped to when targeting Cosmos.
///
/// The property.
///
- /// The the property name that the property is mapped to when targeting Cosmos.
+ /// The for the property name that the property is mapped to when targeting Cosmos.
///
public static ConfigurationSource? GetJsonPropertyNameConfigurationSource(this IConventionProperty property)
=> property.FindAnnotation(CosmosAnnotationNames.PropertyName)?.GetConfigurationSource();
+
+ ///
+ /// Returns the definition of the vector stored in this property.
+ ///
+ /// The property.
+ /// Returns the definition of the vector stored in this property.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static CosmosVectorType? GetVectorType(this IReadOnlyProperty property)
+ => (CosmosVectorType?)property[CosmosAnnotationNames.VectorType];
+
+ ///
+ /// Sets the definition of the vector stored in this property.
+ ///
+ /// The property.
+ /// The type of vector stored in the property.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static void SetVectorType(this IMutableProperty property, CosmosVectorType? vectorType)
+ => property.SetOrRemoveAnnotation(CosmosAnnotationNames.VectorType, vectorType);
+
+ ///
+ /// Sets the definition of the vector stored in this property.
+ ///
+ /// The property.
+ /// The type of vector stored in the property.
+ /// Indicates whether the configuration was specified using a data annotation.
+ /// The configured value.
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static CosmosVectorType? SetVectorType(this IConventionProperty property, CosmosVectorType? vectorType, bool fromDataAnnotation = false)
+ => (CosmosVectorType?)property.SetOrRemoveAnnotation(
+ CosmosAnnotationNames.VectorType,
+ vectorType,
+ fromDataAnnotation)?.Value;
+
+ ///
+ /// Gets the for the definition of the vector stored in this property.
+ ///
+ /// The property.
+ ///
+ /// The for the definition of the vector stored in this property.
+ ///
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public static ConfigurationSource? GetVectorTypeConfigurationSource(this IConventionProperty property)
+ => property.FindAnnotation(CosmosAnnotationNames.VectorType)?.GetConfigurationSource();
}
diff --git a/src/EFCore.Cosmos/Extensions/DistanceFunction.cs b/src/EFCore.Cosmos/Extensions/DistanceFunction.cs
new file mode 100644
index 00000000000..2df582e5ca1
--- /dev/null
+++ b/src/EFCore.Cosmos/Extensions/DistanceFunction.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.Serialization;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.Azure.Cosmos;
+
+///
+/// Defines the distance function for a vector index specification in the Azure Cosmos DB service.
+/// Warning: this type will be replaced by the type from the Cosmos SDK, when it is available.
+///
+///
+/// for usage.
+[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+public enum DistanceFunction
+{
+ ///
+ /// Represents the Euclidean distance function.
+ ///
+ [EnumMember(Value = "euclidean")]
+ Euclidean,
+
+ ///
+ /// Represents the cosine distance function.
+ ///
+ [EnumMember(Value = "cosine")]
+ Cosine,
+
+ ///
+ /// Represents the dot product distance function.
+ ///
+ [EnumMember(Value = "dotproduct")]
+ DotProduct,
+}
diff --git a/src/EFCore.Cosmos/Extensions/Embedding.cs b/src/EFCore.Cosmos/Extensions/Embedding.cs
new file mode 100644
index 00000000000..f700da1653c
--- /dev/null
+++ b/src/EFCore.Cosmos/Extensions/Embedding.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.Azure.Cosmos;
+
+[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+internal class Embedding : IEquatable
+{
+ public string? Path { get; set; }
+ public VectorDataType DataType { get; set; }
+ public int Dimensions { get; set; }
+ public DistanceFunction DistanceFunction { get; set; }
+ public bool Equals(Embedding? that)
+ => Equals(Path, that?.Path) && Equals(DataType, that?.DataType) && Equals(Dimensions, that.Dimensions);
+}
diff --git a/src/EFCore.Cosmos/Extensions/VectorDataType.cs b/src/EFCore.Cosmos/Extensions/VectorDataType.cs
new file mode 100644
index 00000000000..cb547fd1b3a
--- /dev/null
+++ b/src/EFCore.Cosmos/Extensions/VectorDataType.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.Serialization;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.Azure.Cosmos;
+
+///
+/// Defines the target data type of a vector index specification in the Azure Cosmos DB service.
+/// Warning: this type will be replaced by the type from the Cosmos SDK, when it is available.
+///
+[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+public enum VectorDataType
+{
+ ///
+ /// Represents a 16-bit floating point data type.
+ ///
+ [EnumMember(Value = "float16")]
+ Float16,
+
+ ///
+ /// Represents a 32-bit floating point data type.
+ ///
+ [EnumMember(Value = "float32")]
+ Float32,
+
+ ///
+ /// Represents an unsigned 8-bit binary data type.
+ ///
+ [EnumMember(Value = "uint8")]
+ Uint8,
+
+ ///
+ /// Represents a signed 8-bit binary data type.
+ ///
+ [EnumMember(Value = "int8")]
+ Int8,
+}
diff --git a/src/EFCore.Cosmos/Extensions/VectorIndexPath.cs b/src/EFCore.Cosmos/Extensions/VectorIndexPath.cs
new file mode 100644
index 00000000000..8a575c2c40f
--- /dev/null
+++ b/src/EFCore.Cosmos/Extensions/VectorIndexPath.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.Azure.Cosmos;
+
+[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+internal sealed class VectorIndexPath
+{
+ public string? Path { get; set; }
+ public VectorIndexType Type { get; set; }
+}
diff --git a/src/EFCore.Cosmos/Extensions/VectorIndexType.cs b/src/EFCore.Cosmos/Extensions/VectorIndexType.cs
new file mode 100644
index 00000000000..88f71d32a5d
--- /dev/null
+++ b/src/EFCore.Cosmos/Extensions/VectorIndexType.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.Serialization;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.Azure.Cosmos;
+
+///
+/// Defines the target index type of the vector index path specification in the Azure Cosmos DB service.
+/// Warning: this type will be replaced by the type from the Cosmos SDK, when it is available.
+///
+[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+public enum VectorIndexType
+{
+ ///
+ /// Represents a flat vector index type.
+ ///
+ [EnumMember(Value = "flat")]
+ Flat,
+
+ ///
+ /// Represents a Disk ANN vector index type.
+ ///
+ [EnumMember(Value = "diskANN")]
+ // ReSharper disable once InconsistentNaming
+ DiskANN,
+
+ ///
+ /// Represents a quantized flat vector index type.
+ ///
+ [EnumMember(Value = "quantizedFlat")]
+ QuantizedFlat,
+}
diff --git a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs
index 221bf89d345..f02e6b77aa9 100644
--- a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs
+++ b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosModelValidator.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Cosmos.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
@@ -120,14 +121,18 @@ protected virtual void ValidateSharedContainerCompatibility(
{
throw new InvalidOperationException(
CosmosStrings.OwnedTypeDifferentContainer(
- entityType.DisplayName(), ownership.PrincipalEntityType.DisplayName(), container));
+ entityType.DisplayName(),
+ ownership.PrincipalEntityType.DisplayName(),
+ container));
}
if (entityType.GetContainingPropertyName() != null)
{
throw new InvalidOperationException(
CosmosStrings.ContainerContainingPropertyConflict(
- entityType.DisplayName(), container, entityType.GetContainingPropertyName()));
+ entityType.DisplayName(),
+ container,
+ entityType.GetContainingPropertyName()));
}
if (!containers.TryGetValue(container, out var mappedTypes))
@@ -196,8 +201,12 @@ protected virtual void ValidateSharedContainerCompatibility(
{
throw new InvalidOperationException(
CosmosStrings.PartitionKeyStoreNameMismatch(
- firstEntityType.GetPartitionKeyPropertyNames()[i], firstEntityType.DisplayName(), partitionKeyStoreNames[i],
- entityType.GetPartitionKeyPropertyNames()[i], entityType.DisplayName(), storeNames[i]));
+ firstEntityType.GetPartitionKeyPropertyNames()[i],
+ firstEntityType.DisplayName(),
+ partitionKeyStoreNames[i],
+ entityType.GetPartitionKeyPropertyNames()[i],
+ entityType.DisplayName(),
+ storeNames[i]));
}
}
}
@@ -212,22 +221,23 @@ protected virtual void ValidateSharedContainerCompatibility(
{
if (entityType.FindDiscriminatorProperty() == null)
{
- throw new InvalidOperationException(
- CosmosStrings.NoDiscriminatorProperty(entityType.DisplayName(), container));
+ throw new InvalidOperationException(CosmosStrings.NoDiscriminatorProperty(entityType.DisplayName(), container));
}
var discriminatorValue = entityType.GetDiscriminatorValue();
if (discriminatorValue == null)
{
- throw new InvalidOperationException(
- CosmosStrings.NoDiscriminatorValue(entityType.DisplayName(), container));
+ throw new InvalidOperationException(CosmosStrings.NoDiscriminatorValue(entityType.DisplayName(), container));
}
if (discriminatorValues.TryGetValue(discriminatorValue, out var duplicateEntityType))
{
throw new InvalidOperationException(
CosmosStrings.DuplicateDiscriminatorValue(
- entityType.DisplayName(), discriminatorValue, duplicateEntityType.DisplayName(), container));
+ entityType.DisplayName(),
+ discriminatorValue,
+ duplicateEntityType.DisplayName(),
+ container));
}
discriminatorValues[discriminatorValue] = entityType;
@@ -258,7 +268,10 @@ protected virtual void ValidateSharedContainerCompatibility(
var conflictingEntityType = mappedTypes.First(et => et.GetAnalyticalStoreTimeToLive() != null);
throw new InvalidOperationException(
CosmosStrings.AnalyticalTTLMismatch(
- analyticalTtl, conflictingEntityType.DisplayName(), entityType.DisplayName(), currentAnalyticalTtl,
+ analyticalTtl,
+ conflictingEntityType.DisplayName(),
+ entityType.DisplayName(),
+ currentAnalyticalTtl,
container));
}
}
@@ -275,7 +288,11 @@ protected virtual void ValidateSharedContainerCompatibility(
var conflictingEntityType = mappedTypes.First(et => et.GetDefaultTimeToLive() != null);
throw new InvalidOperationException(
CosmosStrings.DefaultTTLMismatch(
- defaultTtl, conflictingEntityType.DisplayName(), entityType.DisplayName(), currentDefaultTtl, container));
+ defaultTtl,
+ conflictingEntityType.DisplayName(),
+ entityType.DisplayName(),
+ currentDefaultTtl,
+ container));
}
}
@@ -292,8 +309,10 @@ protected virtual void ValidateSharedContainerCompatibility(
var conflictingEntityType = mappedTypes.First(et => et.GetThroughput() != null);
throw new InvalidOperationException(
CosmosStrings.ThroughputMismatch(
- throughput.AutoscaleMaxThroughput ?? throughput.Throughput, conflictingEntityType.DisplayName(),
- entityType.DisplayName(), currentThroughput.AutoscaleMaxThroughput ?? currentThroughput.Throughput,
+ throughput.AutoscaleMaxThroughput ?? throughput.Throughput,
+ conflictingEntityType.DisplayName(),
+ entityType.DisplayName(),
+ currentThroughput.AutoscaleMaxThroughput ?? currentThroughput.Throughput,
container));
}
else if ((throughput.AutoscaleMaxThroughput == null)
@@ -308,8 +327,7 @@ protected virtual void ValidateSharedContainerCompatibility(
: conflictingEntityType;
throw new InvalidOperationException(
- CosmosStrings.ThroughputTypeMismatch(
- manualType.DisplayName(), autoscaleType.DisplayName(), container));
+ CosmosStrings.ThroughputTypeMismatch(manualType.DisplayName(), autoscaleType.DisplayName(), container));
}
}
}
@@ -334,16 +352,14 @@ protected virtual void ValidateOnlyETagConcurrencyToken(
var storeName = property.GetJsonPropertyName();
if (storeName != "_etag")
{
- throw new InvalidOperationException(
- CosmosStrings.NonETagConcurrencyToken(entityType.DisplayName(), storeName));
+ throw new InvalidOperationException(CosmosStrings.NonETagConcurrencyToken(entityType.DisplayName(), storeName));
}
var etagType = property.GetTypeMapping().Converter?.ProviderClrType ?? property.ClrType;
if (etagType != typeof(string))
{
throw new InvalidOperationException(
- CosmosStrings.ETagNonStringStoreType(
- property.Name, entityType.DisplayName(), etagType.ShortDisplayName()));
+ CosmosStrings.ETagNonStringStoreType(property.Name, entityType.DisplayName(), etagType.ShortDisplayName()));
}
}
}
@@ -381,8 +397,7 @@ protected virtual void ValidateKeys(
if (idType != typeof(string))
{
throw new InvalidOperationException(
- CosmosStrings.IdNonStringStoreType(
- idProperty.Name, entityType.DisplayName(), idType.ShortDisplayName()));
+ CosmosStrings.IdNonStringStoreType(idProperty.Name, entityType.DisplayName(), idType.ShortDisplayName()));
}
var partitionKeyPropertyNames = entityType.GetPartitionKeyPropertyNames();
@@ -416,7 +431,9 @@ protected virtual void ValidateKeys(
{
throw new InvalidOperationException(
CosmosStrings.PartitionKeyBadStoreType(
- partitionKeyPropertyName, entityType.DisplayName(), partitionKeyType.ShortDisplayName()));
+ partitionKeyPropertyName,
+ entityType.DisplayName(),
+ partitionKeyType.ShortDisplayName()));
}
}
}
@@ -535,10 +552,58 @@ protected virtual void ValidateIndexes(
{
foreach (var index in entityType.GetDeclaredIndexes())
{
- throw new InvalidOperationException(
- CosmosStrings.IndexesExist(
- entityType.DisplayName(),
- string.Join(",", index.Properties.Select(e => e.Name))));
+ if (index.FindAnnotation(CosmosAnnotationNames.VectorIndexType) != null)
+ {
+ if (index.Properties.Count > 1)
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.CompositeVectorIndex(
+ entityType.DisplayName(),
+ string.Join(",", index.Properties.Select(e => e.Name))));
+ }
+
+ if (index.Properties[0].FindAnnotation(CosmosAnnotationNames.VectorType) == null)
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.VectorIndexOnNonVector(
+ entityType.DisplayName(),
+ index.Properties[0].Name));
+ }
+ }
+ else
+ {
+ throw new InvalidOperationException(
+ CosmosStrings.IndexesExist(
+ entityType.DisplayName(),
+ string.Join(",", index.Properties.Select(e => e.Name))));
+ }
+ }
+ }
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ protected override void ValidatePropertyMapping(
+ IModel model,
+ IDiagnosticsLogger logger)
+ {
+ base.ValidatePropertyMapping(model, logger);
+
+ foreach (var entityType in model.GetEntityTypes())
+ {
+ foreach (var property in entityType.GetDeclaredProperties())
+ {
+ var cosmosVectorType = property.GetVectorType();
+ if (cosmosVectorType is not null)
+ {
+ // Will throw if the data type is not set and cannot be inferred.
+ CosmosVectorType.CreateDefaultVectorDataType(property.ClrType);
+ }
}
}
}
diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationNames.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationNames.cs
index 02e2af7ff1a..251e2bdd2a3 100644
--- a/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationNames.cs
+++ b/src/EFCore.Cosmos/Metadata/Internal/CosmosAnnotationNames.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics.CodeAnalysis;
+
namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
///
@@ -43,6 +45,24 @@ public static class CosmosAnnotationNames
///
public const string PartitionKeyNames = Prefix + "PartitionKeyNames";
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public const string VectorIndexType = Prefix + "VectorIndexType";
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ [Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+ public const string VectorType = Prefix + "VectorType";
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosVectorType.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosVectorType.cs
new file mode 100644
index 00000000000..f7d22521deb
--- /dev/null
+++ b/src/EFCore.Cosmos/Metadata/Internal/CosmosVectorType.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.EntityFrameworkCore.Cosmos.Internal;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
+
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+public sealed record class CosmosVectorType(DistanceFunction DistanceFunction, int Dimensions)
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static VectorDataType CreateDefaultVectorDataType(Type clrType)
+ {
+ var elementType = clrType.TryGetElementType(typeof(ReadOnlyMemory<>))?.UnwrapNullableType()
+ ?? clrType.TryGetElementType(typeof(IEnumerable<>))?.UnwrapNullableType();
+
+ return elementType == typeof(sbyte)
+ ? VectorDataType.Int8
+ : elementType == typeof(byte)
+ ? VectorDataType.Uint8
+ : elementType == typeof(float)
+ ? VectorDataType.Float32
+ : throw new InvalidOperationException(CosmosStrings.BadVectorDataType(clrType.ShortDisplayName()));
+ }
+}
diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
index 4fecefac02b..8237cb5e734 100644
--- a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
+++ b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
@@ -47,12 +47,28 @@ public static string BadDictionaryType(object? givenType, object? dictionaryType
GetString("BadDictionaryType", nameof(givenType), nameof(dictionaryType)),
givenType, dictionaryType);
+ ///
+ /// The type '{clrType}' is being used as a vector, but the vector data type cannot be inferred. Only 'ReadOnlyMemory<byte>, ReadOnlyMemory<sbyte>, ReadOnlyMemory<float>, byte[], sbyte[], and float[] are supported.
+ ///
+ public static string BadVectorDataType(object? clrType)
+ => string.Format(
+ GetString("BadVectorDataType", nameof(clrType)),
+ clrType);
+
///
/// The Cosmos database does not support 'CanConnect' or 'CanConnectAsync'.
///
public static string CanConnectNotSupported
=> GetString("CanConnectNotSupported");
+ ///
+ /// A vector index on '{entityType}' is defined over properties `{properties}`. A vector index can only target a single property.
+ ///
+ public static string CompositeVectorIndex(object? entityType, object? properties)
+ => string.Format(
+ GetString("CompositeVectorIndex", nameof(entityType), nameof(properties)),
+ entityType, properties);
+
///
/// Complex projections in subqueries are currently unsupported.
///
@@ -297,6 +313,12 @@ public static string NoReadItemQueryString(object? resourceId, object? partition
public static string NoSubqueryPushdown
=> GetString("NoSubqueryPushdown");
+ ///
+ /// Container configuration for embeddings is not yet supported by the Cosmos SDK. Instead, configure the container manually. See https://aka.ms/ef-cosmos-vectors for more information.
+ ///
+ public static string NoVectorContainerConfig
+ => GetString("NoVectorContainerConfig");
+
///
/// The expression '{sqlExpression}' in the SQL tree does not have a type mapping assigned.
///
@@ -469,6 +491,20 @@ public static string UpdateStoreException(object? itemId)
GetString("UpdateStoreException", nameof(itemId)),
itemId);
+ ///
+ /// A vector index is defined for `{entityType}.{property}`, but this property has not been configured as a vector. Use 'IsVector()' in 'OnModelCreating' to configure the property as a vector.
+ ///
+ public static string VectorIndexOnNonVector(object? entityType, object? property)
+ => string.Format(
+ GetString("VectorIndexOnNonVector", nameof(entityType), nameof(property)),
+ entityType, property);
+
+ ///
+ /// The 'VectorDistance' function can only be used with a property mapped as a vector. Use 'IsVector()' in 'OnModelCreating' to configure the property as a vector.
+ ///
+ public static string VectorSearchRequiresVector
+ => GetString("VectorSearchRequiresVector");
+
///
/// 'VisitChildren' must be overridden in the class deriving from 'SqlExpression'.
///
diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.resx b/src/EFCore.Cosmos/Properties/CosmosStrings.resx
index 7e0388b0571..dca83b25d1c 100644
--- a/src/EFCore.Cosmos/Properties/CosmosStrings.resx
+++ b/src/EFCore.Cosmos/Properties/CosmosStrings.resx
@@ -126,12 +126,18 @@
The type '{givenType}' cannot be mapped as a dictionary because it does not implement '{dictionaryType}'.
+
+ The type '{clrType}' is being used as a vector, but the vector data type cannot be inferred. Only 'ReadOnlyMemory<byte>, ReadOnlyMemory<sbyte>, ReadOnlyMemory<float>, byte[], sbyte[], and float[] are supported.
+
The Cosmos database does not support 'CanConnect' or 'CanConnectAsync'.Complex projections in subqueries are currently unsupported.
+
+ A vector index on '{entityType}' is defined over properties `{properties}`. A vector index can only target a single property.
+
None of connection string, CredentialToken, account key or account endpoint were specified. Specify a set of connection details.
@@ -268,6 +274,9 @@
Azure Cosmos DB does not have an appropriate subquery for this translation.
+
+ Container configuration for embeddings is not yet supported by the Cosmos SDK. Instead, configure the container manually. See https://aka.ms/ef-cosmos-vectors for more information.
+
The expression '{sqlExpression}' in the SQL tree does not have a type mapping assigned.
@@ -340,6 +349,12 @@
An error occurred while saving the item with id '{itemId}'. See the inner exception for details.
+
+ A vector index is defined for `{entityType}.{property}`, but this property has not been configured as a vector. Use 'IsVector()' in 'OnModelCreating' to configure the property as a vector.
+
+
+ The 'VectorDistance' function can only be used with a property mapped as a vector. Use 'IsVector()' in 'OnModelCreating' to configure the property as a vector.
+
'VisitChildren' must be overridden in the class deriving from 'SqlExpression'.
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosMethodCallTranslatorProvider.cs b/src/EFCore.Cosmos/Query/Internal/CosmosMethodCallTranslatorProvider.cs
index 0d871e9230a..352e3d443e1 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosMethodCallTranslatorProvider.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosMethodCallTranslatorProvider.cs
@@ -22,6 +22,7 @@ public class CosmosMethodCallTranslatorProvider : IMethodCallTranslatorProvider
///
public CosmosMethodCallTranslatorProvider(
ISqlExpressionFactory sqlExpressionFactory,
+ ITypeMappingSource typeMappingSource,
IEnumerable plugins)
{
_plugins.AddRange(plugins.SelectMany(p => p.Translators));
@@ -34,7 +35,8 @@ public CosmosMethodCallTranslatorProvider(
new CosmosRandomTranslator(sqlExpressionFactory),
new CosmosRegexTranslator(sqlExpressionFactory),
new CosmosStringMethodTranslator(sqlExpressionFactory),
- new CosmosTypeCheckingTranslator(sqlExpressionFactory)
+ new CosmosTypeCheckingTranslator(sqlExpressionFactory),
+ new CosmosVectorSearchTranslator(sqlExpressionFactory, typeMappingSource)
//new LikeTranslator(sqlExpressionFactory),
//new EnumHasFlagTranslator(sqlExpressionFactory),
//new GetValueOrDefaultTranslator(sqlExpressionFactory),
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs
index 392b29accb2..6dd2963c6e1 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosQuerySqlGenerator.cs
@@ -626,6 +626,19 @@ protected override Expression VisitSqlConstant(SqlConstantExpression sqlConstant
return sqlConstantExpression;
}
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override Expression VisitFragment(FragmentExpression fragmentExpression)
+ {
+ _sqlBuilder.Append(fragmentExpression.Fragment);
+
+ return fragmentExpression;
+ }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -657,11 +670,13 @@ protected override Expression VisitSqlParameter(SqlParameterExpression sqlParame
if (_sqlParameters.All(sp => sp.Name != parameterName))
{
- Check.DebugAssert(sqlParameterExpression.TypeMapping is not null, "SqlParameterExpression without a type mapping");
- var jToken = ((CosmosTypeMapping)sqlParameterExpression.TypeMapping)
- .GenerateJToken(_parameterValues[sqlParameterExpression.Name]);
+ Check.DebugAssert(sqlParameterExpression.TypeMapping is not null, "SqlParameterExpression without a type mapping.");
- _sqlParameters.Add(new SqlParameter(parameterName, jToken));
+ _sqlParameters.Add(
+ new SqlParameter(
+ parameterName,
+ ((CosmosTypeMapping)sqlParameterExpression.TypeMapping)
+ .GenerateJToken(_parameterValues[sqlParameterExpression.Name])));
}
_sqlBuilder.Append(parameterName);
diff --git a/src/EFCore.Cosmos/Query/Internal/Expressions/FragmentExpression.cs b/src/EFCore.Cosmos/Query/Internal/Expressions/FragmentExpression.cs
new file mode 100644
index 00000000000..0a14e2f7430
--- /dev/null
+++ b/src/EFCore.Cosmos/Query/Internal/Expressions/FragmentExpression.cs
@@ -0,0 +1,54 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
+
+///
+/// An expression that represents a fragment that will be inserted verbatim into the query.
+///
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+[DebuggerDisplay("{Microsoft.EntityFrameworkCore.Query.ExpressionPrinter.Print(this), nq}")]
+public class FragmentExpression(string fragment) : Expression, IPrintableExpression
+{
+ ///
+ /// The fragment.
+ ///
+ public virtual string Fragment { get; } = fragment;
+
+ ///
+ protected override Expression VisitChildren(ExpressionVisitor visitor)
+ => this;
+
+ ///
+ public virtual void Print(ExpressionPrinter expressionPrinter)
+ => expressionPrinter.Append(Fragment);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected virtual bool Equals(FragmentExpression other)
+ => base.Equals(other)
+ && Fragment == other.Fragment;
+
+ ///
+ public override bool Equals(object? obj)
+ => !ReferenceEquals(null, obj)
+ && (ReferenceEquals(this, obj)
+ || obj.GetType() == GetType()
+ && Equals((FragmentExpression)obj));
+
+ ///
+ public override int GetHashCode()
+ => HashCode.Combine(base.GetHashCode(), Fragment);
+}
diff --git a/src/EFCore.Cosmos/Query/Internal/SqlExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/SqlExpressionVisitor.cs
index d95ec7eb170..22d2b286bb1 100644
--- a/src/EFCore.Cosmos/Query/Internal/SqlExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/SqlExpressionVisitor.cs
@@ -34,6 +34,7 @@ ShapedQueryExpression shapedQueryExpression
SqlBinaryExpression sqlBinaryExpression => VisitSqlBinary(sqlBinaryExpression),
ObjectBinaryExpression objectBinaryExpression => VisitObjectBinary(objectBinaryExpression),
SqlConstantExpression sqlConstantExpression => VisitSqlConstant(sqlConstantExpression),
+ FragmentExpression jsonFragmentExpression => VisitFragment(jsonFragmentExpression),
SqlUnaryExpression sqlUnaryExpression => VisitSqlUnary(sqlUnaryExpression),
SqlConditionalExpression sqlConditionalExpression => VisitSqlConditional(sqlConditionalExpression),
SqlParameterExpression sqlParameterExpression => VisitSqlParameter(sqlParameterExpression),
@@ -172,6 +173,14 @@ ShapedQueryExpression shapedQueryExpression
///
protected abstract Expression VisitSqlConstant(SqlConstantExpression sqlConstantExpression);
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected abstract Expression VisitFragment(FragmentExpression fragmentExpression);
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
diff --git a/src/EFCore.Cosmos/Query/Internal/Translators/CosmosVectorSearchTranslator.cs b/src/EFCore.Cosmos/Query/Internal/Translators/CosmosVectorSearchTranslator.cs
new file mode 100644
index 00000000000..9c6e62d02a2
--- /dev/null
+++ b/src/EFCore.Cosmos/Query/Internal/Translators/CosmosVectorSearchTranslator.cs
@@ -0,0 +1,87 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.EntityFrameworkCore.Cosmos.Extensions;
+using Microsoft.EntityFrameworkCore.Cosmos.Internal;
+using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
+
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+public class CosmosVectorSearchTranslator(ISqlExpressionFactory sqlExpressionFactory, ITypeMappingSource typeMappingSource)
+ : IMethodCallTranslator
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public SqlExpression? Translate(
+ SqlExpression? instance,
+ MethodInfo method,
+ IReadOnlyList arguments,
+ IDiagnosticsLogger logger)
+ {
+ if (method.DeclaringType != typeof(CosmosDbFunctionsExtensions)
+ && method.Name != nameof(CosmosDbFunctionsExtensions.VectorDistance))
+ {
+ return null;
+ }
+
+ var vectorMapping = arguments[1].TypeMapping as CosmosVectorTypeMapping
+ ?? arguments[2].TypeMapping as CosmosVectorTypeMapping
+ ?? throw new InvalidOperationException(CosmosStrings.VectorSearchRequiresVector);
+
+ Check.DebugAssert(arguments.Count is 3 or 4 or 5, "Did you add a parameter?");
+
+ SqlConstantExpression bruteForce;
+ if (arguments.Count >= 4)
+ {
+ if (arguments[3] is not SqlConstantExpression { Value: bool })
+ {
+ throw new InvalidOperationException(
+ CoreStrings.ArgumentNotConstant("useBruteForce", nameof(CosmosDbFunctionsExtensions.VectorDistance)));
+ }
+
+ bruteForce = (SqlConstantExpression)arguments[3];
+ }
+ else
+ {
+ bruteForce = (SqlConstantExpression)sqlExpressionFactory.Constant(false);
+ }
+
+ var vectorType = vectorMapping.VectorType;
+ if (arguments.Count == 5)
+ {
+ if (arguments[4] is not SqlConstantExpression { Value: DistanceFunction distanceFunction })
+ {
+ throw new InvalidOperationException(
+ CoreStrings.ArgumentNotConstant("distanceFunction", nameof(CosmosDbFunctionsExtensions.VectorDistance)));
+ }
+
+ vectorType = vectorType with { DistanceFunction = distanceFunction };
+ }
+
+ var dataType = CosmosVectorType.CreateDefaultVectorDataType(vectorMapping.ClrType);
+
+ return sqlExpressionFactory.Function(
+ "VectorDistance",
+ [
+ sqlExpressionFactory.ApplyTypeMapping(arguments[1], vectorMapping),
+ sqlExpressionFactory.ApplyTypeMapping(arguments[2], vectorMapping),
+ bruteForce,
+ new FragmentExpression(
+ $"{{'distanceFunction':'{vectorType.DistanceFunction.ToString().ToLower()}', 'dataType':'{dataType.ToString().ToLower()}'}}")
+ ],
+ typeof(double),
+ typeMappingSource.FindMapping(typeof(double))!);
+ }
+}
diff --git a/src/EFCore.Cosmos/Storage/Internal/ContainerProperties.cs b/src/EFCore.Cosmos/Storage/Internal/ContainerProperties.cs
index d0761cd9ecb..78965ed3073 100644
--- a/src/EFCore.Cosmos/Storage/Internal/ContainerProperties.cs
+++ b/src/EFCore.Cosmos/Storage/Internal/ContainerProperties.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
+
namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
///
@@ -9,85 +11,11 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
-public readonly record struct ContainerProperties
-{
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public readonly string Id;
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public readonly IReadOnlyList PartitionKeyStoreNames;
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public readonly int? AnalyticalStoreTimeToLiveInSeconds;
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public readonly int? DefaultTimeToLive;
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public readonly ThroughputProperties? Throughput;
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public ContainerProperties(
- string containerId,
- IReadOnlyList partitionKeyStoreNames,
- int? analyticalTtl,
- int? defaultTtl,
- ThroughputProperties? throughput)
- {
- Id = containerId;
- PartitionKeyStoreNames = partitionKeyStoreNames;
- AnalyticalStoreTimeToLiveInSeconds = analyticalTtl;
- DefaultTimeToLive = defaultTtl;
- Throughput = throughput;
- }
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public void Deconstruct(
- out string containerId,
- out IReadOnlyList partitionKeyStoreNames,
- out int? analyticalTtl,
- out int? defaultTtl,
- out ThroughputProperties? throughput)
- {
- containerId = Id;
- partitionKeyStoreNames = PartitionKeyStoreNames;
- analyticalTtl = AnalyticalStoreTimeToLiveInSeconds;
- defaultTtl = DefaultTimeToLive;
- throughput = Throughput;
- }
-}
+public readonly record struct ContainerProperties(
+ string Id,
+ IReadOnlyList PartitionKeyStoreNames,
+ int? AnalyticalStoreTimeToLiveInSeconds,
+ int? DefaultTimeToLive,
+ ThroughputProperties? Throughput,
+ IReadOnlyList Indexes,
+ IReadOnlyList<(IProperty Property, CosmosVectorType VectorType)> Vectors);
diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs
index 2c6cc8625ef..d72a889c1a3 100644
--- a/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs
+++ b/src/EFCore.Cosmos/Storage/Internal/CosmosClientWrapper.cs
@@ -2,11 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
+using System.Collections.ObjectModel;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.EntityFrameworkCore.Cosmos.Diagnostics.Internal;
using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal;
+using Microsoft.EntityFrameworkCore.Cosmos.Internal;
+using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Internal;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -234,13 +237,56 @@ private static async Task CreateContainerIfNotExistsOnceAsync(
{
var (parameters, wrapper) = parametersTuple;
var partitionKeyPaths = parameters.PartitionKeyStoreNames.Select(e => "/" + e).ToList();
- var response = await wrapper.Client.GetDatabase(wrapper._databaseId).CreateContainerIfNotExistsAsync(
- new Azure.Cosmos.ContainerProperties(parameters.Id, partitionKeyPaths)
+
+ var vectorIndexes = new Collection();
+ foreach (var index in parameters.Indexes)
+ {
+ var vectorIndexType = (VectorIndexType?)index.FindAnnotation(CosmosAnnotationNames.VectorIndexType)?.Value;
+ if (vectorIndexType != null)
+ {
+ // Model validation will ensure there is only one property.
+ Check.DebugAssert(index.Properties.Count == 1, "Vector index must have one property.");
+
+ vectorIndexes.Add(
+ new VectorIndexPath { Path = "/" + index.Properties[0].GetJsonPropertyName(), Type = vectorIndexType.Value });
+ }
+ }
+
+ var embeddings = new Collection();
+ foreach (var tuple in parameters.Vectors)
+ {
+ embeddings.Add(
+ new Embedding
{
- PartitionKeyDefinitionVersion = PartitionKeyDefinitionVersion.V2,
- DefaultTimeToLive = parameters.DefaultTimeToLive,
- AnalyticalStoreTimeToLiveInSeconds = parameters.AnalyticalStoreTimeToLiveInSeconds
- },
+ Path = "/" + tuple.Property.GetJsonPropertyName(),
+ DataType = CosmosVectorType.CreateDefaultVectorDataType(tuple.Property.ClrType),
+ Dimensions = tuple.VectorType.Dimensions,
+ DistanceFunction = tuple.VectorType.DistanceFunction
+ });
+ }
+
+ var containerProperties = new Azure.Cosmos.ContainerProperties(parameters.Id, partitionKeyPaths)
+ {
+ PartitionKeyDefinitionVersion = PartitionKeyDefinitionVersion.V2,
+ DefaultTimeToLive = parameters.DefaultTimeToLive,
+ AnalyticalStoreTimeToLiveInSeconds = parameters.AnalyticalStoreTimeToLiveInSeconds,
+ };
+
+ // TODO: Enable these once they are available in the Cosmos SDK. See #33783.
+ if (embeddings.Any())
+ {
+ throw new InvalidOperationException(CosmosStrings.NoVectorContainerConfig);
+ //containerProperties.VectorEmbeddingPolicy = new VectorEmbeddingPolicy(embeddings);
+ }
+
+ if (vectorIndexes.Any())
+ {
+ throw new InvalidOperationException(CosmosStrings.NoVectorContainerConfig);
+ //containerProperties.IndexingPolicy = new IndexingPolicy { VectorIndexes = vectorIndexes };
+ }
+
+ var response = await wrapper.Client.GetDatabase(wrapper._databaseId).CreateContainerIfNotExistsAsync(
+ containerProperties,
throughput: parameters.Throughput?.Throughput,
cancellationToken: cancellationToken)
.ConfigureAwait(false);
diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
index e46852477e3..d55b7a63f9e 100644
--- a/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
+++ b/src/EFCore.Cosmos/Storage/Internal/CosmosDatabaseCreator.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.EntityFrameworkCore.Cosmos.Internal;
+using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
@@ -112,6 +113,8 @@ private static IEnumerable GetContainersToCreate(IModel mod
int? analyticalTtl = null;
int? defaultTtl = null;
ThroughputProperties? throughput = null;
+ var indexes = new List();
+ var vectors = new List<(IProperty Property, CosmosVectorType VectorType)>();
foreach (var entityType in mappedTypes)
{
@@ -122,6 +125,15 @@ private static IEnumerable GetContainersToCreate(IModel mod
analyticalTtl ??= entityType.GetAnalyticalStoreTimeToLive();
defaultTtl ??= entityType.GetDefaultTimeToLive();
throughput ??= entityType.GetThroughput();
+ indexes.AddRange(entityType.GetIndexes());
+
+ foreach (var property in entityType.GetProperties())
+ {
+ if (property.FindTypeMapping() is CosmosVectorTypeMapping vectorTypeMapping)
+ {
+ vectors.Add((property, vectorTypeMapping.VectorType));
+ }
+ }
}
yield return new ContainerProperties(
@@ -129,7 +141,9 @@ private static IEnumerable GetContainersToCreate(IModel mod
partitionKeyStoreNames,
analyticalTtl,
defaultTtl,
- throughput);
+ throughput,
+ indexes,
+ vectors);
}
}
@@ -212,11 +226,16 @@ public virtual Task CanConnectAsync(CancellationToken cancellationToken =
=> throw new NotSupportedException(CosmosStrings.CanConnectNotSupported);
///
+ /// Returns the store names of the properties that is used to store the partition keys.
+ ///
+ ///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
+ ///
+ /// The entity type to get the partition key property names for.
+ /// The names of the partition key property.
private static IReadOnlyList GetPartitionKeyStoreNames(IEntityType entityType)
{
var properties = entityType.GetPartitionKeyProperties();
diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs
index b4055de9e77..7588e248520 100644
--- a/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs
+++ b/src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs
@@ -1,9 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal;
+using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Microsoft.EntityFrameworkCore.Storage.Json;
using Newtonsoft.Json.Linq;
@@ -39,6 +39,24 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
};
}
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public override CoreTypeMapping? FindMapping(IProperty property)
+ // A provider should typically not override this because using the property directly causes problems with Migrations where
+ // the property does not exist. However, since the Cosmos provider doesn't have Migrations, it should be okay to use the property
+ // directly.
+ => base.FindMapping(property) switch
+ {
+ CosmosTypeMapping mapping when property.FindAnnotation(CosmosAnnotationNames.VectorType)?.Value is CosmosVectorType vectorType
+ => new CosmosVectorTypeMapping(mapping, vectorType),
+ var other => other
+ };
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -61,6 +79,15 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
{
var clrType = mappingInfo.ClrType!;
+ var memoryType = clrType.TryGetElementType(typeof(ReadOnlyMemory<>));
+ if (memoryType != null)
+ {
+ return new CosmosTypeMapping(clrType)
+ .WithComposedConverter(
+ (ValueConverter)Activator.CreateInstance(typeof(ReadOnlyMemoryConverter<>).MakeGenericType(memoryType))!,
+ (ValueComparer)Activator.CreateInstance(typeof(ReadOnlyMemoryComparer<>).MakeGenericType(memoryType))!);
+ }
+
return clrType.IsNumeric()
|| clrType == typeof(bool)
|| clrType == typeof(DateOnly)
@@ -87,12 +114,19 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
// First attempt to resolve this as a primitive collection (e.g. List). This does not handle Dictionary.
if (TryFindJsonCollectionMapping(
- mappingInfo, clrType, providerClrType: null, ref elementMapping, out var elementComparer,
+ mappingInfo,
+ clrType,
+ providerClrType: null,
+ ref elementMapping,
+ out var elementComparer,
out var collectionReaderWriter)
&& elementMapping is not null)
{
return new CosmosTypeMapping(
- clrType, elementComparer, elementMapping: elementMapping, jsonValueReaderWriter: collectionReaderWriter);
+ clrType,
+ elementComparer,
+ elementMapping: elementMapping,
+ jsonValueReaderWriter: collectionReaderWriter);
}
// Next, attempt to resolve this as a dictionary (e.g. Dictionary).
diff --git a/src/EFCore.Cosmos/Storage/Internal/CosmosVectorTypeMapping.cs b/src/EFCore.Cosmos/Storage/Internal/CosmosVectorTypeMapping.cs
new file mode 100644
index 00000000000..717e7f92d34
--- /dev/null
+++ b/src/EFCore.Cosmos/Storage/Internal/CosmosVectorTypeMapping.cs
@@ -0,0 +1,120 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal;
+using Microsoft.EntityFrameworkCore.Storage.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
+
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+[Experimental(EFDiagnostics.CosmosVectorSearchExperimental)]
+public class CosmosVectorTypeMapping : CosmosTypeMapping
+{
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static new CosmosVectorTypeMapping Default { get; }
+ // Note that this default is not valid because dimensions cannot be zero. But since there is no reasonable
+ // default dimensions size for a vector type, this is intentionally not valid rather than just being wrong.
+ // The fundamental problem here is that type mappings are "required" to have some default now.
+ = new(typeof(byte[]), new CosmosVectorType(DistanceFunction.Cosine, 0));
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public CosmosVectorTypeMapping(
+ Type clrType,
+ CosmosVectorType vectorType,
+ ValueComparer? comparer = null,
+ ValueComparer? keyComparer = null,
+ CoreTypeMapping? elementMapping = null,
+ JsonValueReaderWriter? jsonValueReaderWriter = null)
+ : this(
+ new CoreTypeMappingParameters(
+ clrType,
+ converter: null,
+ comparer,
+ keyComparer,
+ elementMapping: elementMapping,
+ jsonValueReaderWriter: jsonValueReaderWriter),
+ vectorType)
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public CosmosVectorTypeMapping(CosmosTypeMapping mapping, CosmosVectorType vectorType)
+ : this(
+ new CoreTypeMappingParameters(
+ mapping.ClrType,
+ converter: mapping.Converter,
+ mapping.Comparer,
+ mapping.KeyComparer,
+ elementMapping: mapping.ElementTypeMapping,
+ jsonValueReaderWriter: mapping.JsonValueReaderWriter),
+ vectorType)
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected CosmosVectorTypeMapping(CoreTypeMappingParameters parameters, CosmosVectorType vectorType)
+ : base(parameters)
+ {
+ VectorType = vectorType;
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public virtual CosmosVectorType VectorType { get; }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public override CoreTypeMapping WithComposedConverter(
+ ValueConverter? converter,
+ ValueComparer? comparer = null,
+ ValueComparer? keyComparer = null,
+ CoreTypeMapping? elementMapping = null,
+ JsonValueReaderWriter? jsonValueReaderWriter = null)
+ => new CosmosVectorTypeMapping(
+ Parameters.WithComposedConverter(converter, comparer, keyComparer, elementMapping, jsonValueReaderWriter),
+ VectorType);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ protected override CoreTypeMapping Clone(CoreTypeMappingParameters parameters)
+ => new CosmosVectorTypeMapping(parameters, VectorType);
+}
diff --git a/src/EFCore.Cosmos/Storage/Internal/ReadOnlyMemoryComparer.cs b/src/EFCore.Cosmos/Storage/Internal/ReadOnlyMemoryComparer.cs
new file mode 100644
index 00000000000..04fc97b36cf
--- /dev/null
+++ b/src/EFCore.Cosmos/Storage/Internal/ReadOnlyMemoryComparer.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
+
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+public class ReadOnlyMemoryComparer() : ValueComparer>(false);
diff --git a/src/EFCore.Cosmos/Storage/Internal/ReadOnlyMemoryConverter.cs b/src/EFCore.Cosmos/Storage/Internal/ReadOnlyMemoryConverter.cs
new file mode 100644
index 00000000000..e086b4ccaa0
--- /dev/null
+++ b/src/EFCore.Cosmos/Storage/Internal/ReadOnlyMemoryConverter.cs
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
+
+///
+/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+/// the same compatibility standards as public APIs. It may be changed or removed without notice in
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+public class ReadOnlyMemoryConverter : ValueConverter, T[]>
+{
+ private static readonly ConverterMappingHints DefaultHints = new();
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public ReadOnlyMemoryConverter()
+ : this(null)
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public ReadOnlyMemoryConverter(ConverterMappingHints? mappingHints)
+ : base(
+ v => ToArray(v),
+ v => ToMemory(v),
+ DefaultHints.With(mappingHints))
+ {
+ }
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static T[] ToArray(ReadOnlyMemory memory)
+ => memory.ToArray();
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static ReadOnlyMemory ToMemory(T[] array)
+ // If the array is empty, then return the default ReadOnlyMemory instance because this will compare the same as other empty
+ // ReadOnlyMemory instances, while the instance created with an empty array is considered not equal to the default.
+ => array.Length == 0 ? default : new(array);
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public static ValueConverterInfo DefaultInfo { get; }
+ = new(typeof(ReadOnlyMemory), typeof(T[]), i => new ReadOnlyMemoryConverter(i.MappingHints), DefaultHints);
+}
diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs
index a71d12b602f..1a9d3320fa9 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -114,6 +114,14 @@ public static string AnnotationNotFound(object? annotation, object? annotatable)
GetString("AnnotationNotFound", nameof(annotation), nameof(annotatable)),
annotation, annotatable);
+ ///
+ /// The '{parameter}' value passed to '{methodName}' must be a constant.
+ ///
+ public static string ArgumentNotConstant(object? parameter, object? methodName)
+ => string.Format(
+ GetString("ArgumentNotConstant", nameof(parameter), nameof(methodName)),
+ parameter, methodName);
+
///
/// The property '{property}' of the argument '{argument}' cannot be null.
///
diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx
index 34cac8e6dd1..41d62da4122 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -150,6 +150,9 @@
The annotation '{annotation}' was not found. Ensure that the annotation has been added to the object {annotatable}
+
+ The '{parameter}' value passed to '{methodName}' must be a constant.
+
The property '{property}' of the argument '{argument}' cannot be null.
diff --git a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Basic_cosmos_model/DataEntityType.cs b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Basic_cosmos_model/DataEntityType.cs
index 8f10c2b7aef..5bfe0266a7a 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Basic_cosmos_model/DataEntityType.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Scaffolding/Baselines/Basic_cosmos_model/DataEntityType.cs
@@ -33,7 +33,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
"Microsoft.EntityFrameworkCore.Scaffolding.CompiledModelTestBase+Data",
typeof(CompiledModelTestBase.Data),
baseEntityType,
- propertyCount: 8,
+ propertyCount: 9,
keyCount: 1);
var id = runtimeEntityType.AddProperty(
@@ -162,20 +162,53 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
byte[] (string v) => Convert.FromBase64String(v))));
blob.AddAnnotation("Cosmos:PropertyName", "JsonBlob");
+ var bytes = runtimeEntityType.AddProperty(
+ "Bytes",
+ typeof(ReadOnlyMemory));
+ bytes.SetAccessors(
+ ReadOnlyMemory (InternalEntityEntry entry) => entry.ReadShadowValue>(2),
+ ReadOnlyMemory (InternalEntityEntry entry) => entry.ReadShadowValue>(2),
+ ReadOnlyMemory (InternalEntityEntry entry) => entry.ReadOriginalValue>(bytes, 3),
+ ReadOnlyMemory (InternalEntityEntry entry) => entry.GetCurrentValue>(bytes),
+ object (ValueBuffer valueBuffer) => valueBuffer[3]);
+ bytes.SetPropertyIndexes(
+ index: 3,
+ originalValueIndex: 3,
+ shadowIndex: 2,
+ relationshipIndex: -1,
+ storeGenerationIndex: -1);
+ bytes.TypeMapping = CosmosTypeMapping.Default.Clone(
+ comparer: new ValueComparer>(
+ bool (ReadOnlyMemory v1, ReadOnlyMemory v2) => v1.Equals(v2),
+ int (ReadOnlyMemory v) => ((object)v).GetHashCode(),
+ ReadOnlyMemory (ReadOnlyMemory v) => v),
+ keyComparer: new ValueComparer>(
+ bool (ReadOnlyMemory v1, ReadOnlyMemory v2) => v1.Equals(v2),
+ int (ReadOnlyMemory v) => ((object)v).GetHashCode(),
+ ReadOnlyMemory (ReadOnlyMemory v) => v),
+ providerValueComparer: new ValueComparer(
+ bool (byte[] v1, byte[] v2) => StructuralComparisons.StructuralEqualityComparer.Equals(((object)(v1)), ((object)(v2))),
+ int (byte[] v) => StructuralComparisons.StructuralEqualityComparer.GetHashCode(((object)(v))),
+ byte[] (byte[] source) => source.ToArray()),
+ converter: new ValueConverter, byte[]>(
+ byte[] (ReadOnlyMemory v) => ReadOnlyMemoryConverter.ToArray(v),
+ ReadOnlyMemory (byte[] v) => ReadOnlyMemoryConverter.ToMemory(v)));
+ bytes.SetSentinelFromProviderValue(new byte[0]);
+
var list = runtimeEntityType.AddProperty(
"List",
typeof(List>),
nullable: true);
list.SetAccessors(
- List> (InternalEntityEntry entry) => entry.ReadShadowValue>>(2),
- List> (InternalEntityEntry entry) => entry.ReadShadowValue>>(2),
- List> (InternalEntityEntry entry) => entry.ReadOriginalValue>>(list, 3),
+ List> (InternalEntityEntry entry) => entry.ReadShadowValue>>(3),
+ List> (InternalEntityEntry entry) => entry.ReadShadowValue>>(3),
+ List> (InternalEntityEntry entry) => entry.ReadOriginalValue>>(list, 4),
List> (InternalEntityEntry entry) => entry.GetCurrentValue>>(list),
- object (ValueBuffer valueBuffer) => valueBuffer[3]);
+ object (ValueBuffer valueBuffer) => valueBuffer[4]);
list.SetPropertyIndexes(
- index: 3,
- originalValueIndex: 3,
- shadowIndex: 2,
+ index: 4,
+ originalValueIndex: 4,
+ shadowIndex: 3,
relationshipIndex: -1,
storeGenerationIndex: -1);
list.TypeMapping = CosmosTypeMapping.Default.Clone(
@@ -217,15 +250,15 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
typeof(Dictionary),
nullable: true);
map.SetAccessors(
- Dictionary (InternalEntityEntry entry) => entry.ReadShadowValue>(3),
- Dictionary (InternalEntityEntry entry) => entry.ReadShadowValue>(3),
- Dictionary (InternalEntityEntry entry) => entry.ReadOriginalValue>(map, 4),
+ Dictionary (InternalEntityEntry entry) => entry.ReadShadowValue>(4),
+ Dictionary (InternalEntityEntry entry) => entry.ReadShadowValue>(4),
+ Dictionary (InternalEntityEntry entry) => entry.ReadOriginalValue>(map, 5),
Dictionary (InternalEntityEntry entry) => entry.GetCurrentValue>(map),
- object (ValueBuffer valueBuffer) => valueBuffer[4]);
+ object (ValueBuffer valueBuffer) => valueBuffer[5]);
map.SetPropertyIndexes(
- index: 4,
- originalValueIndex: 4,
- shadowIndex: 3,
+ index: 5,
+ originalValueIndex: 5,
+ shadowIndex: 4,
relationshipIndex: -1,
storeGenerationIndex: -1);
map.TypeMapping = CosmosTypeMapping.Default.Clone(
@@ -252,15 +285,15 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
afterSaveBehavior: PropertySaveBehavior.Throw,
valueGeneratorFactory: new IdValueGeneratorFactory().Create);
__id.SetAccessors(
- string (InternalEntityEntry entry) => entry.ReadShadowValue(4),
- string (InternalEntityEntry entry) => entry.ReadShadowValue(4),
- string (InternalEntityEntry entry) => entry.ReadOriginalValue(__id, 5),
+ string (InternalEntityEntry entry) => entry.ReadShadowValue(5),
+ string (InternalEntityEntry entry) => entry.ReadShadowValue(5),
+ string (InternalEntityEntry entry) => entry.ReadOriginalValue(__id, 6),
string (InternalEntityEntry entry) => entry.GetCurrentValue(__id),
- object (ValueBuffer valueBuffer) => valueBuffer[5]);
+ object (ValueBuffer valueBuffer) => valueBuffer[6]);
__id.SetPropertyIndexes(
- index: 5,
- originalValueIndex: 5,
- shadowIndex: 4,
+ index: 6,
+ originalValueIndex: 6,
+ shadowIndex: 5,
relationshipIndex: -1,
storeGenerationIndex: -1);
__id.TypeMapping = CosmosTypeMapping.Default.Clone(
@@ -288,15 +321,15 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
beforeSaveBehavior: PropertySaveBehavior.Ignore,
afterSaveBehavior: PropertySaveBehavior.Ignore);
__jObject.SetAccessors(
- JObject (InternalEntityEntry entry) => (entry.FlaggedAsStoreGenerated(6) ? entry.ReadStoreGeneratedValue(0) : (entry.FlaggedAsTemporary(6) && entry.ReadShadowValue(5) == null ? entry.ReadTemporaryValue(0) : entry.ReadShadowValue(5))),
- JObject (InternalEntityEntry entry) => entry.ReadShadowValue(5),
- JObject (InternalEntityEntry entry) => entry.ReadOriginalValue(__jObject, 6),
+ JObject (InternalEntityEntry entry) => (entry.FlaggedAsStoreGenerated(7) ? entry.ReadStoreGeneratedValue(0) : (entry.FlaggedAsTemporary(7) && entry.ReadShadowValue(6) == null ? entry.ReadTemporaryValue(0) : entry.ReadShadowValue(6))),
+ JObject (InternalEntityEntry entry) => entry.ReadShadowValue(6),
+ JObject (InternalEntityEntry entry) => entry.ReadOriginalValue(__jObject, 7),
JObject (InternalEntityEntry entry) => entry.GetCurrentValue(__jObject),
- object (ValueBuffer valueBuffer) => valueBuffer[6]);
+ object (ValueBuffer valueBuffer) => valueBuffer[7]);
__jObject.SetPropertyIndexes(
- index: 6,
- originalValueIndex: 6,
- shadowIndex: 5,
+ index: 7,
+ originalValueIndex: 7,
+ shadowIndex: 6,
relationshipIndex: -1,
storeGenerationIndex: 0);
__jObject.TypeMapping = CosmosTypeMapping.Default.Clone(
@@ -324,15 +357,15 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas
beforeSaveBehavior: PropertySaveBehavior.Ignore,
afterSaveBehavior: PropertySaveBehavior.Ignore);
_etag.SetAccessors(
- string (InternalEntityEntry entry) => (entry.FlaggedAsStoreGenerated(7) ? entry.ReadStoreGeneratedValue(1) : (entry.FlaggedAsTemporary(7) && entry.ReadShadowValue(6) == null ? entry.ReadTemporaryValue(1) : entry.ReadShadowValue(6))),
- string (InternalEntityEntry entry) => entry.ReadShadowValue(6),
- string (InternalEntityEntry entry) => entry.ReadOriginalValue(_etag, 7),
+ string (InternalEntityEntry entry) => (entry.FlaggedAsStoreGenerated(8) ? entry.ReadStoreGeneratedValue(1) : (entry.FlaggedAsTemporary(8) && entry.ReadShadowValue(7) == null ? entry.ReadTemporaryValue(1) : entry.ReadShadowValue(7))),
+ string (InternalEntityEntry entry) => entry.ReadShadowValue(7),
+ string (InternalEntityEntry entry) => entry.ReadOriginalValue(_etag, 8),
string (InternalEntityEntry entry) => entry.GetCurrentValue(_etag),
- object (ValueBuffer valueBuffer) => valueBuffer[7]);
+ object (ValueBuffer valueBuffer) => valueBuffer[8]);
_etag.SetPropertyIndexes(
- index: 7,
- originalValueIndex: 7,
- shadowIndex: 6,
+ index: 8,
+ originalValueIndex: 8,
+ shadowIndex: 7,
relationshipIndex: -1,
storeGenerationIndex: 1);
_etag.TypeMapping = CosmosTypeMapping.Default.Clone(
@@ -363,6 +396,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
var id = runtimeEntityType.FindProperty("Id")!;
var partitionId = runtimeEntityType.FindProperty("PartitionId")!;
var blob = runtimeEntityType.FindProperty("Blob")!;
+ var bytes = runtimeEntityType.FindProperty("Bytes")!;
var list = runtimeEntityType.FindProperty("List")!;
var map = runtimeEntityType.FindProperty("Map")!;
var __id = runtimeEntityType.FindProperty("__id")!;
@@ -375,16 +409,16 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
ISnapshot (InternalEntityEntry source) =>
{
var entity = ((CompiledModelTestBase.Data)(source.Entity));
- return ((ISnapshot)(new Snapshot>, Dictionary, string, JObject, string>(((ValueComparer)(((IProperty)id).GetValueComparer())).Snapshot(source.GetCurrentValue(id)), (source.GetCurrentValue(partitionId) == null ? null : ((ValueComparer)(((IProperty)partitionId).GetValueComparer())).Snapshot(source.GetCurrentValue(partitionId))), (source.GetCurrentValue(blob) == null ? null : ((ValueComparer)(((IProperty)blob).GetValueComparer())).Snapshot(source.GetCurrentValue(blob))), (((object)(source.GetCurrentValue>>(list))) == null ? null : ((List>)(((ValueComparer