From 16e3d1f2f5bbee336c444df4abc380878f87d169 Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Wed, 25 Feb 2026 18:32:13 +0100 Subject: [PATCH] [MEVD] Add validation for mismatched collection key Closes #11355 --- .../Memory/MongoDB/MongoModelBuilder.cs | 2 + .../AzureAISearch/AzureAISearchCollection.cs | 2 +- .../AzureAISearchDynamicModelBuilder.cs | 2 + .../AzureAISearchModelBuilder.cs | 2 + .../CosmosMongoDB/CosmosMongoCollection.cs | 2 +- .../CosmosNoSql/CosmosNoSqlCollection.cs | 2 +- .../CosmosNoSql/CosmosNoSqlModelBuilder.cs | 7 +++ .../VectorData/InMemory/InMemoryCollection.cs | 2 +- .../InMemory/InMemoryModelBuilder.cs | 2 + .../src/VectorData/MongoDB/MongoCollection.cs | 2 +- .../VectorData/PgVector/PostgresCollection.cs | 2 +- .../PgVector/PostgresModelBuilder.cs | 2 + .../VectorData/Pinecone/PineconeCollection.cs | 2 +- .../Pinecone/PineconeModelBuilder.cs | 2 + .../src/VectorData/Qdrant/QdrantCollection.cs | 2 +- .../VectorData/Qdrant/QdrantModelBuilder.cs | 2 + .../Redis/RedisHashSetCollection.cs | 2 +- .../VectorData/Redis/RedisJsonCollection.cs | 1 + .../Redis/RedisJsonDynamicModelBuilder.cs | 2 + .../VectorData/Redis/RedisJsonModelBuilder.cs | 2 + .../src/VectorData/Redis/RedisModelBuilder.cs | 2 + .../SqlServer/SqlServerCollection.cs | 2 +- .../SqlServer/SqlServerModelBuilder.cs | 2 + .../VectorData/SqliteVec/SqliteCollection.cs | 2 +- .../SqliteVec/SqliteModelBuilder.cs | 2 + .../CollectionJsonModelBuilder.cs | 7 +-- .../CollectionModelBuilder.cs | 50 +++++++++++++------ .../VectorData/Weaviate/WeaviateCollection.cs | 2 +- .../Weaviate/WeaviateModelBuilder.cs | 2 + ...CosmosMongoCollectionSearchMappingTests.cs | 3 +- .../MongoDB.UnitTests/MongoMapperTests.cs | 2 +- .../QdrantCollectionSearchMappingTests.cs | 1 + .../Qdrant.UnitTests/QdrantMapperTests.cs | 16 +++--- .../RedisHashSetMapperTests.cs | 2 +- .../Redis.UnitTests/RedisJsonMapperTests.cs | 8 +-- .../TypeTests/KeyTypeTests.cs | 13 +++++ .../CollectionModelBuilderTests.cs | 19 +++---- .../Weaviate.UnitTests/WeaviateMapperTests.cs | 1 + 38 files changed, 126 insertions(+), 54 deletions(-) diff --git a/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoModelBuilder.cs b/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoModelBuilder.cs index 7d6138bb9524..69f974cffd19 100644 --- a/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoModelBuilder.cs +++ b/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoModelBuilder.cs @@ -46,6 +46,8 @@ protected override bool SupportsKeyAutoGeneration(Type keyPropertyType) protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + base.ValidateKeyProperty(keyProperty); + var type = keyProperty.Type; if (type != typeof(string) && type != typeof(int) && type != typeof(long) && type != typeof(Guid) && type != typeof(ObjectId)) diff --git a/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollection.cs b/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollection.cs index 7b841bf29f91..bf9eb58c8536 100644 --- a/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollection.cs +++ b/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollection.cs @@ -72,7 +72,7 @@ public AzureAISearchCollection(SearchIndexClient searchIndexClient, string name, static options => typeof(TRecord) == typeof(Dictionary) ? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(AzureAISearchDynamicCollection))) : new AzureAISearchModelBuilder() - .Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator, options.JsonSerializerOptions ?? JsonSerializerOptions.Default), + .Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator, options.JsonSerializerOptions ?? JsonSerializerOptions.Default), options) { } diff --git a/dotnet/src/VectorData/AzureAISearch/AzureAISearchDynamicModelBuilder.cs b/dotnet/src/VectorData/AzureAISearch/AzureAISearchDynamicModelBuilder.cs index 34b02f7ae58f..2ddd797ef6a4 100644 --- a/dotnet/src/VectorData/AzureAISearch/AzureAISearchDynamicModelBuilder.cs +++ b/dotnet/src/VectorData/AzureAISearch/AzureAISearchDynamicModelBuilder.cs @@ -18,6 +18,8 @@ internal class AzureAISearchDynamicModelBuilder() : CollectionModelBuilder(s_mod protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + base.ValidateKeyProperty(keyProperty); + var type = keyProperty.Type; if (type != typeof(string) && type != typeof(Guid)) diff --git a/dotnet/src/VectorData/AzureAISearch/AzureAISearchModelBuilder.cs b/dotnet/src/VectorData/AzureAISearch/AzureAISearchModelBuilder.cs index 2ca1d0c809c8..4b0bed52eaf6 100644 --- a/dotnet/src/VectorData/AzureAISearch/AzureAISearchModelBuilder.cs +++ b/dotnet/src/VectorData/AzureAISearch/AzureAISearchModelBuilder.cs @@ -22,6 +22,8 @@ internal class AzureAISearchModelBuilder() : CollectionJsonModelBuilder(s_modelB protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + base.ValidateKeyProperty(keyProperty); + var type = keyProperty.Type; if (type != typeof(string) && type != typeof(Guid)) diff --git a/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoCollection.cs b/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoCollection.cs index a277d60b83d6..6e9fdefcf7b3 100644 --- a/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoCollection.cs +++ b/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoCollection.cs @@ -90,7 +90,7 @@ public CosmosMongoCollection( name, static options => typeof(TRecord) == typeof(Dictionary) ? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(CosmosMongoDynamicCollection))) - : new MongoModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator), + : new MongoModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator), options) { } diff --git a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlCollection.cs b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlCollection.cs index df0eac3a766b..36509560d037 100644 --- a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlCollection.cs +++ b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlCollection.cs @@ -124,7 +124,7 @@ internal CosmosNoSqlCollection( static options => typeof(TRecord) == typeof(Dictionary) ? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(CosmosNoSqlDynamicCollection))) : new CosmosNoSqlModelBuilder() - .Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator, options.JsonSerializerOptions ?? JsonSerializerOptions.Default), + .Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator, options.JsonSerializerOptions ?? JsonSerializerOptions.Default), options) { } diff --git a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlModelBuilder.cs b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlModelBuilder.cs index 2185f5152a15..8ad263ae6105 100644 --- a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlModelBuilder.cs +++ b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlModelBuilder.cs @@ -22,6 +22,13 @@ internal class CosmosNoSqlModelBuilder() : CollectionJsonModelBuilder(s_modelBui protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + // CosmosNoSqlKey is a composite key type (document ID + partition key) that doesn't correspond to any single key property type; + // skip the base TKey-to-key-property validation when it's used. + if (this.KeyType != typeof(CosmosNoSqlKey)) + { + base.ValidateKeyProperty(keyProperty); + } + // Note that the key property in Cosmos NoSQL refers to the document ID, not to the CosmosNoSqlKey structure which includes both // the document ID and the partition key (and which is the generic TKey type parameter of the collection). var type = keyProperty.Type; diff --git a/dotnet/src/VectorData/InMemory/InMemoryCollection.cs b/dotnet/src/VectorData/InMemory/InMemoryCollection.cs index a9ea78f18dc6..16ae2b43fd0e 100644 --- a/dotnet/src/VectorData/InMemory/InMemoryCollection.cs +++ b/dotnet/src/VectorData/InMemory/InMemoryCollection.cs @@ -56,7 +56,7 @@ public InMemoryCollection(string name, InMemoryCollectionOptions? options = defa name, static options => typeof(TRecord) == typeof(Dictionary) ? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(InMemoryDynamicCollection))) - : new InMemoryModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator), + : new InMemoryModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator), options) { } diff --git a/dotnet/src/VectorData/InMemory/InMemoryModelBuilder.cs b/dotnet/src/VectorData/InMemory/InMemoryModelBuilder.cs index 817490611f46..75b36ada096c 100644 --- a/dotnet/src/VectorData/InMemory/InMemoryModelBuilder.cs +++ b/dotnet/src/VectorData/InMemory/InMemoryModelBuilder.cs @@ -19,6 +19,8 @@ internal class InMemoryModelBuilder() : CollectionModelBuilder(ValidationOptions protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + base.ValidateKeyProperty(keyProperty); + // All .NET types are supported by the InMemory provider, but we support auto-generation of keys only for GUIDs if (keyProperty.IsAutoGenerated && keyProperty.Type != typeof(Guid)) { diff --git a/dotnet/src/VectorData/MongoDB/MongoCollection.cs b/dotnet/src/VectorData/MongoDB/MongoCollection.cs index 2995f8acaf2d..b172a70bec61 100644 --- a/dotnet/src/VectorData/MongoDB/MongoCollection.cs +++ b/dotnet/src/VectorData/MongoDB/MongoCollection.cs @@ -99,7 +99,7 @@ public MongoCollection( name, static options => typeof(TRecord) == typeof(Dictionary) ? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(MongoDynamicCollection))) - : new MongoModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator), + : new MongoModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator), options) { } diff --git a/dotnet/src/VectorData/PgVector/PostgresCollection.cs b/dotnet/src/VectorData/PgVector/PostgresCollection.cs index 14d9cef81f69..0705559eda9d 100644 --- a/dotnet/src/VectorData/PgVector/PostgresCollection.cs +++ b/dotnet/src/VectorData/PgVector/PostgresCollection.cs @@ -92,7 +92,7 @@ internal PostgresCollection(NpgsqlDataSource dataSource, NpgsqlDataSourceArc? da name, static options => typeof(TRecord) == typeof(Dictionary) ? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(PostgresDynamicCollection))) - : new PostgresModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator), + : new PostgresModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator), options) { } diff --git a/dotnet/src/VectorData/PgVector/PostgresModelBuilder.cs b/dotnet/src/VectorData/PgVector/PostgresModelBuilder.cs index 0db578dbb64d..4a9e68ab053c 100644 --- a/dotnet/src/VectorData/PgVector/PostgresModelBuilder.cs +++ b/dotnet/src/VectorData/PgVector/PostgresModelBuilder.cs @@ -26,6 +26,8 @@ protected override bool SupportsKeyAutoGeneration(Type keyPropertyType) protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + base.ValidateKeyProperty(keyProperty); + var type = keyProperty.Type; if (type != typeof(short) diff --git a/dotnet/src/VectorData/Pinecone/PineconeCollection.cs b/dotnet/src/VectorData/Pinecone/PineconeCollection.cs index 84ed8e9d591a..b3680adabdb6 100644 --- a/dotnet/src/VectorData/Pinecone/PineconeCollection.cs +++ b/dotnet/src/VectorData/Pinecone/PineconeCollection.cs @@ -66,7 +66,7 @@ public PineconeCollection(PineconeClient pineconeClient, string name, PineconeCo name, static options => typeof(TRecord) == typeof(Dictionary) ? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(PineconeDynamicCollection))) - : new PineconeModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator), + : new PineconeModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator), options) { } diff --git a/dotnet/src/VectorData/Pinecone/PineconeModelBuilder.cs b/dotnet/src/VectorData/Pinecone/PineconeModelBuilder.cs index fd0565fc72ce..400ae4faa22e 100644 --- a/dotnet/src/VectorData/Pinecone/PineconeModelBuilder.cs +++ b/dotnet/src/VectorData/Pinecone/PineconeModelBuilder.cs @@ -20,6 +20,8 @@ internal class PineconeModelBuilder() : CollectionModelBuilder(s_validationOptio protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + base.ValidateKeyProperty(keyProperty); + var type = keyProperty.Type; if (type != typeof(string) && type != typeof(Guid)) diff --git a/dotnet/src/VectorData/Qdrant/QdrantCollection.cs b/dotnet/src/VectorData/Qdrant/QdrantCollection.cs index 2cab60d6eb4a..7b94a2ade552 100644 --- a/dotnet/src/VectorData/Qdrant/QdrantCollection.cs +++ b/dotnet/src/VectorData/Qdrant/QdrantCollection.cs @@ -90,7 +90,7 @@ internal QdrantCollection(Func clientFactory, string name, name, static options => typeof(TRecord) == typeof(Dictionary) ? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(QdrantDynamicCollection))) - : new QdrantModelBuilder(options.HasNamedVectors).Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator), + : new QdrantModelBuilder(options.HasNamedVectors).Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator), options) { } diff --git a/dotnet/src/VectorData/Qdrant/QdrantModelBuilder.cs b/dotnet/src/VectorData/Qdrant/QdrantModelBuilder.cs index 98cf07c8b791..57d5c86bbdea 100644 --- a/dotnet/src/VectorData/Qdrant/QdrantModelBuilder.cs +++ b/dotnet/src/VectorData/Qdrant/QdrantModelBuilder.cs @@ -21,6 +21,8 @@ private static CollectionModelBuildingOptions GetModelBuildOptions(bool hasNamed protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + base.ValidateKeyProperty(keyProperty); + var type = keyProperty.Type; if (type != typeof(ulong) && type != typeof(Guid)) diff --git a/dotnet/src/VectorData/Redis/RedisHashSetCollection.cs b/dotnet/src/VectorData/Redis/RedisHashSetCollection.cs index 42e4b8b7e63b..64cf40133e29 100644 --- a/dotnet/src/VectorData/Redis/RedisHashSetCollection.cs +++ b/dotnet/src/VectorData/Redis/RedisHashSetCollection.cs @@ -76,7 +76,7 @@ public RedisHashSetCollection(IDatabase database, string name, RedisHashSetColle name, static options => typeof(TRecord) == typeof(Dictionary) ? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(RedisHashSetDynamicCollection))) - : new RedisModelBuilder(ModelBuildingOptions).Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator), + : new RedisModelBuilder(ModelBuildingOptions).Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator), options) { } diff --git a/dotnet/src/VectorData/Redis/RedisJsonCollection.cs b/dotnet/src/VectorData/Redis/RedisJsonCollection.cs index 5c2e327add7d..2a97af440e76 100644 --- a/dotnet/src/VectorData/Redis/RedisJsonCollection.cs +++ b/dotnet/src/VectorData/Redis/RedisJsonCollection.cs @@ -83,6 +83,7 @@ public RedisJsonCollection(IDatabase database, string name, RedisJsonCollectionO : new RedisJsonModelBuilder(ModelBuildingOptions) .Build( typeof(TRecord), + typeof(TKey), options.Definition, options.EmbeddingGenerator, options.JsonSerializerOptions ?? JsonSerializerOptions.Default), diff --git a/dotnet/src/VectorData/Redis/RedisJsonDynamicModelBuilder.cs b/dotnet/src/VectorData/Redis/RedisJsonDynamicModelBuilder.cs index ffebe5637cba..286be9b82696 100644 --- a/dotnet/src/VectorData/Redis/RedisJsonDynamicModelBuilder.cs +++ b/dotnet/src/VectorData/Redis/RedisJsonDynamicModelBuilder.cs @@ -19,6 +19,8 @@ internal class RedisJsonDynamicModelBuilder(CollectionModelBuildingOptions optio protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + base.ValidateKeyProperty(keyProperty); + var type = keyProperty.Type; if (type != typeof(string) && type != typeof(Guid)) diff --git a/dotnet/src/VectorData/Redis/RedisJsonModelBuilder.cs b/dotnet/src/VectorData/Redis/RedisJsonModelBuilder.cs index 44b0cbd996af..1e9106211e31 100644 --- a/dotnet/src/VectorData/Redis/RedisJsonModelBuilder.cs +++ b/dotnet/src/VectorData/Redis/RedisJsonModelBuilder.cs @@ -20,6 +20,8 @@ internal class RedisJsonModelBuilder(CollectionModelBuildingOptions options) : C protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + base.ValidateKeyProperty(keyProperty); + var type = keyProperty.Type; if (type != typeof(string) && type != typeof(Guid)) diff --git a/dotnet/src/VectorData/Redis/RedisModelBuilder.cs b/dotnet/src/VectorData/Redis/RedisModelBuilder.cs index 47ff2d3e6b67..0dba7cc12ef0 100644 --- a/dotnet/src/VectorData/Redis/RedisModelBuilder.cs +++ b/dotnet/src/VectorData/Redis/RedisModelBuilder.cs @@ -22,6 +22,8 @@ internal class RedisModelBuilder(CollectionModelBuildingOptions options) : Colle protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + base.ValidateKeyProperty(keyProperty); + var type = keyProperty.Type; if (type != typeof(string) && type != typeof(Guid)) diff --git a/dotnet/src/VectorData/SqlServer/SqlServerCollection.cs b/dotnet/src/VectorData/SqlServer/SqlServerCollection.cs index 5c3c4b779f77..af0ad4892d24 100644 --- a/dotnet/src/VectorData/SqlServer/SqlServerCollection.cs +++ b/dotnet/src/VectorData/SqlServer/SqlServerCollection.cs @@ -59,7 +59,7 @@ public SqlServerCollection( name, static options => typeof(TRecord) == typeof(Dictionary) ? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(SqlServerDynamicCollection))) - : new SqlServerModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator), + : new SqlServerModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator), options) { } diff --git a/dotnet/src/VectorData/SqlServer/SqlServerModelBuilder.cs b/dotnet/src/VectorData/SqlServer/SqlServerModelBuilder.cs index 19e014b17fe6..34edaeae2a0f 100644 --- a/dotnet/src/VectorData/SqlServer/SqlServerModelBuilder.cs +++ b/dotnet/src/VectorData/SqlServer/SqlServerModelBuilder.cs @@ -26,6 +26,8 @@ protected override bool SupportsKeyAutoGeneration(Type keyPropertyType) protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + base.ValidateKeyProperty(keyProperty); + var type = keyProperty.Type; if (type != typeof(int) && type != typeof(long) && type != typeof(string) && type != typeof(Guid)) diff --git a/dotnet/src/VectorData/SqliteVec/SqliteCollection.cs b/dotnet/src/VectorData/SqliteVec/SqliteCollection.cs index 0aaff587c04f..c2a4bd7b07ad 100644 --- a/dotnet/src/VectorData/SqliteVec/SqliteCollection.cs +++ b/dotnet/src/VectorData/SqliteVec/SqliteCollection.cs @@ -74,7 +74,7 @@ public SqliteCollection( name, static options => typeof(TRecord) == typeof(Dictionary) ? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(SqliteDynamicCollection))) - : new SqliteModelBuilder().Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator), + : new SqliteModelBuilder().Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator), options) { } diff --git a/dotnet/src/VectorData/SqliteVec/SqliteModelBuilder.cs b/dotnet/src/VectorData/SqliteVec/SqliteModelBuilder.cs index d6a3c2df4617..cdfd6e58715d 100644 --- a/dotnet/src/VectorData/SqliteVec/SqliteModelBuilder.cs +++ b/dotnet/src/VectorData/SqliteVec/SqliteModelBuilder.cs @@ -22,6 +22,8 @@ protected override bool SupportsKeyAutoGeneration(Type keyPropertyType) protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + base.ValidateKeyProperty(keyProperty); + var type = keyProperty.Type; if (type != typeof(int) && type != typeof(long) && type != typeof(string) && type != typeof(Guid)) diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionJsonModelBuilder.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionJsonModelBuilder.cs index bab81647514c..2b7584b8dd0c 100644 --- a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionJsonModelBuilder.cs +++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionJsonModelBuilder.cs @@ -27,19 +27,20 @@ protected CollectionJsonModelBuilder(CollectionModelBuildingOptions options) } /// - /// Builds and returns a from the given and . + /// Builds and returns a from the given and . /// [RequiresDynamicCode("This model building variant is not compatible with NativeAOT. See BuildDynamic() for dynamic mapping, and a third variant accepting source-generated delegates will be introduced in the future.")] [RequiresUnreferencedCode("This model building variant is not compatible with trimming. See BuildDynamic() for dynamic mapping, and a third variant accepting source-generated delegates will be introduced in the future.")] public virtual CollectionModel Build( - Type type, + Type recordType, + Type keyType, VectorStoreCollectionDefinition? definition, IEmbeddingGenerator? defaultEmbeddingGenerator, JsonSerializerOptions jsonSerializerOptions) { this._jsonSerializerOptions = jsonSerializerOptions; - return this.Build(type, definition, defaultEmbeddingGenerator); + return this.Build(recordType, keyType, definition, defaultEmbeddingGenerator); } /// diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionModelBuilder.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionModelBuilder.cs index 03dcb9d48e9a..e854f20facb6 100644 --- a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionModelBuilder.cs +++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionModelBuilder.cs @@ -53,6 +53,12 @@ public abstract class CollectionModelBuilder /// protected IEmbeddingGenerator? DefaultEmbeddingGenerator { get; private set; } + /// + /// Gets the collection's generic key type parameter (TKey), if provided. + /// Used by to validate that TKey corresponds to the key property type on the model. + /// + protected Type? KeyType { get; private set; } + /// /// Constructs a new . /// @@ -60,32 +66,38 @@ protected CollectionModelBuilder(CollectionModelBuildingOptions options) => this.Options = options; /// - /// Builds and returns an from the given and . + /// Builds and returns an from the given and . /// + /// The CLR type of the record. + /// The collection's generic key type parameter (TKey), used to validate correspondence with the key property type. + /// An optional record definition that overrides attribute-based configuration. + /// An optional default embedding generator for vector properties. [RequiresDynamicCode("This model building variant is not compatible with NativeAOT. See BuildDynamic() for dynamic mapping, and a third variant accepting source-generated delegates will be introduced in the future.")] [RequiresUnreferencedCode("This model building variant is not compatible with trimming. See BuildDynamic() for dynamic mapping, and a third variant accepting source-generated delegates will be introduced in the future.")] - public virtual CollectionModel Build(Type type, VectorStoreCollectionDefinition? definition, IEmbeddingGenerator? defaultEmbeddingGenerator) + public virtual CollectionModel Build(Type recordType, Type keyType, VectorStoreCollectionDefinition? definition, IEmbeddingGenerator? defaultEmbeddingGenerator) { - if (type == typeof(Dictionary)) + this.KeyType = keyType; + + if (recordType == typeof(Dictionary)) { throw new ArgumentException("Dynamic mapping with Dictionary requires calling BuildDynamic()."); } this.DefaultEmbeddingGenerator = definition?.EmbeddingGenerator ?? defaultEmbeddingGenerator; - this.ProcessTypeProperties(type, definition); + this.ProcessTypeProperties(recordType, definition); if (definition is not null) { - this.ProcessRecordDefinition(definition, type); + this.ProcessRecordDefinition(definition, recordType); } // Go over the properties, set the PropertyInfos to point to the .NET type's properties and validate type compatibility. foreach (var property in this.Properties) { // When we have a CLR type (POCO, not dynamic mapping), get the .NET property's type and make sure it matches the definition. - property.PropertyInfo = type.GetProperty(property.ModelName) - ?? throw new InvalidOperationException($"Property '{property.ModelName}' not found on CLR type '{type.FullName}'."); + property.PropertyInfo = recordType.GetProperty(property.ModelName) + ?? throw new InvalidOperationException($"Property '{property.ModelName}' not found on CLR type '{recordType.FullName}'."); var clrPropertyType = property.PropertyInfo.PropertyType; if ((Nullable.GetUnderlyingType(clrPropertyType) ?? clrPropertyType) != (Nullable.GetUnderlyingType(property.Type) ?? property.Type)) @@ -96,20 +108,22 @@ public virtual CollectionModel Build(Type type, VectorStoreCollectionDefinition? } this.Customize(); - this.Validate(type, definition); + this.Validate(recordType, definition); // Extra validation for non-dynamic mapping scenarios: ensure the type has a parameterless constructor. - if (!this.Options.UsesExternalSerializer && type.GetConstructor(Type.EmptyTypes) is null) + if (!this.Options.UsesExternalSerializer && recordType.GetConstructor(Type.EmptyTypes) is null) { - throw new NotSupportedException($"Type '{type.Name}' must have a parameterless constructor."); + throw new NotSupportedException($"Type '{recordType.Name}' must have a parameterless constructor."); } - return new(type, new ActivatorBasedRecordCreator(), this.KeyProperties, this.DataProperties, this.VectorProperties, this.PropertyMap); + return new(recordType, new ActivatorBasedRecordCreator(), this.KeyProperties, this.DataProperties, this.VectorProperties, this.PropertyMap); } /// /// Builds and returns an for dynamic mapping scenarios from the given . /// + /// The record definition describing the collection's schema. + /// An optional default embedding generator for vector properties. public virtual CollectionModel BuildDynamic(VectorStoreCollectionDefinition definition, IEmbeddingGenerator? defaultEmbeddingGenerator) { if (definition is null) @@ -531,10 +545,18 @@ protected virtual bool SupportsKeyAutoGeneration(Type keyPropertyType) => keyPropertyType == typeof(Guid); /// - /// Validates that the .NET type for a key property is supported by the provider. + /// Validates the key property. The default implementation validates that the collection's generic key type () + /// corresponds to the key property type on the model, if was provided. + /// Provider overrides should call the base implementation. /// - /// true if the provider supports auto-generating keys of the specified type; otherwise, false. - protected abstract void ValidateKeyProperty(KeyPropertyModel keyProperty); + protected virtual void ValidateKeyProperty(KeyPropertyModel keyProperty) + { + if (this.KeyType is not null && this.KeyType != typeof(object) && this.KeyType != keyProperty.Type) + { + throw new InvalidOperationException( + $"The collection's generic key type is '{this.KeyType.Name}', but the key property '{keyProperty.ModelName}' has type '{keyProperty.Type.Name}'. The generic key type must match the key property type."); + } + } /// /// Validates that the .NET type for a data property is supported by the provider. diff --git a/dotnet/src/VectorData/Weaviate/WeaviateCollection.cs b/dotnet/src/VectorData/Weaviate/WeaviateCollection.cs index 542ad250fe16..e23d6067fcc0 100644 --- a/dotnet/src/VectorData/Weaviate/WeaviateCollection.cs +++ b/dotnet/src/VectorData/Weaviate/WeaviateCollection.cs @@ -84,7 +84,7 @@ public WeaviateCollection( static options => typeof(TRecord) == typeof(Dictionary) ? throw new NotSupportedException(VectorDataStrings.NonDynamicCollectionWithDictionaryNotSupported(typeof(WeaviateDynamicCollection))) : new WeaviateModelBuilder(options.HasNamedVectors) - .Build(typeof(TRecord), options.Definition, options.EmbeddingGenerator, WeaviateConstants.s_jsonSerializerOptions), + .Build(typeof(TRecord), typeof(TKey), options.Definition, options.EmbeddingGenerator, WeaviateConstants.s_jsonSerializerOptions), options) { } diff --git a/dotnet/src/VectorData/Weaviate/WeaviateModelBuilder.cs b/dotnet/src/VectorData/Weaviate/WeaviateModelBuilder.cs index 5877c27ec185..bdb27f87b050 100644 --- a/dotnet/src/VectorData/Weaviate/WeaviateModelBuilder.cs +++ b/dotnet/src/VectorData/Weaviate/WeaviateModelBuilder.cs @@ -24,6 +24,8 @@ private static CollectionModelBuildingOptions GetModelBuildingOptions(bool hasNa protected override void ValidateKeyProperty(KeyPropertyModel keyProperty) { + base.ValidateKeyProperty(keyProperty); + if (keyProperty.Type != typeof(Guid)) { throw new NotSupportedException( diff --git a/dotnet/test/VectorData/CosmosMongoDB.UnitTests/CosmosMongoCollectionSearchMappingTests.cs b/dotnet/test/VectorData/CosmosMongoDB.UnitTests/CosmosMongoCollectionSearchMappingTests.cs index 371bf2441d8e..165ad088fa5f 100644 --- a/dotnet/test/VectorData/CosmosMongoDB.UnitTests/CosmosMongoCollectionSearchMappingTests.cs +++ b/dotnet/test/VectorData/CosmosMongoDB.UnitTests/CosmosMongoCollectionSearchMappingTests.cs @@ -103,8 +103,7 @@ public void BuilderFilterByDefaultReturnsValidFilter() private static CollectionModel BuildModel(List properties) => new MongoModelBuilder() - .Build( - typeof(Dictionary), + .BuildDynamic( new() { Properties = properties }, defaultEmbeddingGenerator: null); } diff --git a/dotnet/test/VectorData/MongoDB.UnitTests/MongoMapperTests.cs b/dotnet/test/VectorData/MongoDB.UnitTests/MongoMapperTests.cs index 90096dafbf4f..68ce835de4cb 100644 --- a/dotnet/test/VectorData/MongoDB.UnitTests/MongoMapperTests.cs +++ b/dotnet/test/VectorData/MongoDB.UnitTests/MongoMapperTests.cs @@ -32,7 +32,7 @@ public MongoMapperTests() ] }; - this._sut = new(new MongoModelBuilder().Build(typeof(MongoHotelModel), definition, defaultEmbeddingGenerator: null)); + this._sut = new(new MongoModelBuilder().Build(typeof(MongoHotelModel), typeof(string), definition, defaultEmbeddingGenerator: null)); } [Fact] diff --git a/dotnet/test/VectorData/Qdrant.UnitTests/QdrantCollectionSearchMappingTests.cs b/dotnet/test/VectorData/Qdrant.UnitTests/QdrantCollectionSearchMappingTests.cs index 582391de2df7..bbc5a62e3697 100644 --- a/dotnet/test/VectorData/Qdrant.UnitTests/QdrantCollectionSearchMappingTests.cs +++ b/dotnet/test/VectorData/Qdrant.UnitTests/QdrantCollectionSearchMappingTests.cs @@ -119,6 +119,7 @@ public void MapScoredPointToVectorSearchResultMapsResults() var model = new QdrantModelBuilder(hasNamedVectors: false) .Build( typeof(DataModel), + typeof(ulong), new() { Properties = diff --git a/dotnet/test/VectorData/Qdrant.UnitTests/QdrantMapperTests.cs b/dotnet/test/VectorData/Qdrant.UnitTests/QdrantMapperTests.cs index 92760b9e83b0..ca6f3599f7ae 100644 --- a/dotnet/test/VectorData/Qdrant.UnitTests/QdrantMapperTests.cs +++ b/dotnet/test/VectorData/Qdrant.UnitTests/QdrantMapperTests.cs @@ -24,7 +24,7 @@ public void MapsSinglePropsFromDataToStorageModelWithUlong(bool hasNamedVectors) // Arrange. var definition = CreateSinglePropsVectorStoreRecordDefinition(typeof(ulong)); var model = new QdrantModelBuilder(hasNamedVectors) - .Build(typeof(SinglePropsModel), definition, defaultEmbeddingGenerator: null); + .Build(typeof(SinglePropsModel), typeof(ulong), definition, defaultEmbeddingGenerator: null); var sut = new QdrantMapper>(model, hasNamedVectors); // Act. @@ -54,7 +54,7 @@ public void MapsSinglePropsFromDataToStorageModelWithGuid(bool hasNamedVectors) // Arrange. var definition = CreateSinglePropsVectorStoreRecordDefinition(typeof(Guid)); var model = new QdrantModelBuilder(hasNamedVectors) - .Build(typeof(SinglePropsModel), definition, defaultEmbeddingGenerator: null); + .Build(typeof(SinglePropsModel), typeof(Guid), definition, defaultEmbeddingGenerator: null); var sut = new QdrantMapper>(model, hasNamedVectors); // Act. @@ -77,7 +77,7 @@ public void MapsSinglePropsFromStorageToDataModelWithUlong(bool hasNamedVectors, // Arrange. var definition = CreateSinglePropsVectorStoreRecordDefinition(typeof(ulong)); var model = new QdrantModelBuilder(hasNamedVectors) - .Build(typeof(SinglePropsModel), definition, defaultEmbeddingGenerator: null); + .Build(typeof(SinglePropsModel), typeof(ulong), definition, defaultEmbeddingGenerator: null); var sut = new QdrantMapper>(model, hasNamedVectors); // Act. @@ -109,7 +109,7 @@ public void MapsSinglePropsFromStorageToDataModelWithGuid(bool hasNamedVectors, // Arrange. var definition = CreateSinglePropsVectorStoreRecordDefinition(typeof(Guid)); var model = new QdrantModelBuilder(hasNamedVectors) - .Build(typeof(SinglePropsModel), definition, defaultEmbeddingGenerator: null); + .Build(typeof(SinglePropsModel), typeof(Guid), definition, defaultEmbeddingGenerator: null); var sut = new QdrantMapper>(model, hasNamedVectors); // Act. @@ -137,7 +137,7 @@ public void MapsMultiPropsFromDataToStorageModelWithUlong() // Arrange. var definition = CreateMultiPropsVectorStoreRecordDefinition(typeof(ulong)); var model = new QdrantModelBuilder(hasNamedVectors: true) - .Build(typeof(MultiPropsModel), definition, defaultEmbeddingGenerator: null); + .Build(typeof(MultiPropsModel), typeof(ulong), definition, defaultEmbeddingGenerator: null); var sut = new QdrantMapper>(model, hasNamedVectors: true); @@ -166,7 +166,7 @@ public void MapsMultiPropsFromDataToStorageModelWithGuid() // Arrange. var definition = CreateMultiPropsVectorStoreRecordDefinition(typeof(Guid)); var model = new QdrantModelBuilder(hasNamedVectors: true) - .Build(typeof(MultiPropsModel), definition, defaultEmbeddingGenerator: null); + .Build(typeof(MultiPropsModel), typeof(Guid), definition, defaultEmbeddingGenerator: null); var sut = new QdrantMapper>(model, hasNamedVectors: true); // Act. @@ -196,7 +196,7 @@ public void MapsMultiPropsFromStorageToDataModelWithUlong(bool includeVectors) // Arrange. var definition = CreateMultiPropsVectorStoreRecordDefinition(typeof(ulong)); var model = new QdrantModelBuilder(hasNamedVectors: true) - .Build(typeof(MultiPropsModel), definition, defaultEmbeddingGenerator: null); + .Build(typeof(MultiPropsModel), typeof(ulong), definition, defaultEmbeddingGenerator: null); var sut = new QdrantMapper>(model, hasNamedVectors: true); // Act. @@ -235,7 +235,7 @@ public void MapsMultiPropsFromStorageToDataModelWithGuid(bool includeVectors) // Arrange. var definition = CreateMultiPropsVectorStoreRecordDefinition(typeof(Guid)); var model = new QdrantModelBuilder(hasNamedVectors: true) - .Build(typeof(MultiPropsModel), definition, defaultEmbeddingGenerator: null); + .Build(typeof(MultiPropsModel), typeof(Guid), definition, defaultEmbeddingGenerator: null); var sut = new QdrantMapper>(model, hasNamedVectors: true); // Act. diff --git a/dotnet/test/VectorData/Redis.UnitTests/RedisHashSetMapperTests.cs b/dotnet/test/VectorData/Redis.UnitTests/RedisHashSetMapperTests.cs index 8a6b953f4e80..6d65bc4237f1 100644 --- a/dotnet/test/VectorData/Redis.UnitTests/RedisHashSetMapperTests.cs +++ b/dotnet/test/VectorData/Redis.UnitTests/RedisHashSetMapperTests.cs @@ -16,7 +16,7 @@ public sealed class RedisHashSetMapperTests { private static readonly CollectionModel s_model = new RedisModelBuilder(RedisHashSetCollection.ModelBuildingOptions) - .Build(typeof(AllTypesModel), RedisHashSetMappingTestHelpers.s_definition, defaultEmbeddingGenerator: null); + .Build(typeof(AllTypesModel), typeof(string), RedisHashSetMappingTestHelpers.s_definition, defaultEmbeddingGenerator: null); [Fact] public void MapsAllFieldsFromDataToStorageModel() diff --git a/dotnet/test/VectorData/Redis.UnitTests/RedisJsonMapperTests.cs b/dotnet/test/VectorData/Redis.UnitTests/RedisJsonMapperTests.cs index b771077ead19..3a6f157ec8f7 100644 --- a/dotnet/test/VectorData/Redis.UnitTests/RedisJsonMapperTests.cs +++ b/dotnet/test/VectorData/Redis.UnitTests/RedisJsonMapperTests.cs @@ -20,7 +20,7 @@ public void MapsAllFieldsFromDataToStorageModel() { // Arrange. var model = new RedisJsonModelBuilder(RedisJsonCollection.ModelBuildingOptions) - .Build(typeof(MultiPropsModel), definition: null, defaultEmbeddingGenerator: null, JsonSerializerOptions.Default); + .Build(typeof(MultiPropsModel), typeof(string), definition: null, defaultEmbeddingGenerator: null, JsonSerializerOptions.Default); var sut = new RedisJsonMapper(model, JsonSerializerOptions.Default); // Act. @@ -42,7 +42,7 @@ public void MapsAllFieldsFromDataToStorageModelWithCustomSerializerOptions() // Arrange. var jsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var model = new RedisJsonModelBuilder(RedisJsonCollection.ModelBuildingOptions) - .Build(typeof(MultiPropsModel), definition: null, defaultEmbeddingGenerator: null, jsonSerializerOptions); + .Build(typeof(MultiPropsModel), typeof(string), definition: null, defaultEmbeddingGenerator: null, jsonSerializerOptions); var sut = new RedisJsonMapper(model, jsonSerializerOptions); // Act. @@ -63,7 +63,7 @@ public void MapsAllFieldsFromStorageToDataModel() { // Arrange. var model = new RedisJsonModelBuilder(RedisJsonCollection.ModelBuildingOptions) - .Build(typeof(MultiPropsModel), definition: null, defaultEmbeddingGenerator: null, JsonSerializerOptions.Default); + .Build(typeof(MultiPropsModel), typeof(string), definition: null, defaultEmbeddingGenerator: null, JsonSerializerOptions.Default); var sut = new RedisJsonMapper(model, JsonSerializerOptions.Default); // Act. @@ -91,7 +91,7 @@ public void MapsAllFieldsFromStorageToDataModelWithCustomSerializerOptions() // Arrange. var jsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; var model = new RedisJsonModelBuilder(RedisJsonCollection.ModelBuildingOptions) - .Build(typeof(MultiPropsModel), definition: null, defaultEmbeddingGenerator: null, jsonSerializerOptions); + .Build(typeof(MultiPropsModel), typeof(string), definition: null, defaultEmbeddingGenerator: null, jsonSerializerOptions); var sut = new RedisJsonMapper(model, jsonSerializerOptions); // Act. diff --git a/dotnet/test/VectorData/VectorData.ConformanceTests/TypeTests/KeyTypeTests.cs b/dotnet/test/VectorData/VectorData.ConformanceTests/TypeTests/KeyTypeTests.cs index a10c469591f0..124c5e705869 100644 --- a/dotnet/test/VectorData/VectorData.ConformanceTests/TypeTests/KeyTypeTests.cs +++ b/dotnet/test/VectorData/VectorData.ConformanceTests/TypeTests/KeyTypeTests.cs @@ -18,6 +18,19 @@ public virtual Task Guid() new Guid("603840bf-cf91-4521-8b8e-8b6a2e75910a"), supportsAutoGeneration: true); + /// + /// Verifies that creating a collection with a TKey that doesn't match the key property type on the model throws. + /// + [ConditionalFact] + public virtual void MismatchedKeyTypeThrows() + { + // The definition says the key property is string (matching Record.Key), + // but TKey is Guid - this mismatch should be detected during model building. + Assert.Throws(() => + fixture.TestStore.CreateCollection>( + fixture.CollectionName, fixture.CreateRecordDefinition(withAutoGeneration: false))); + } + protected virtual Task Test(TKey key, bool supportsAutoGeneration = false) where TKey : struct => this.Test(key, default!, supportsAutoGeneration: supportsAutoGeneration); diff --git a/dotnet/test/VectorData/VectorData.UnitTests/CollectionModelBuilderTests.cs b/dotnet/test/VectorData/VectorData.UnitTests/CollectionModelBuilderTests.cs index 1fb1961e7883..d326fd22bcf3 100644 --- a/dotnet/test/VectorData/VectorData.UnitTests/CollectionModelBuilderTests.cs +++ b/dotnet/test/VectorData/VectorData.UnitTests/CollectionModelBuilderTests.cs @@ -21,7 +21,7 @@ public class CollectionModelBuilderTests public void Default_embedding_generator_without_record_definition() { using var embeddingGenerator = new FakeEmbeddingGenerator>(); - var model = new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), definition: null, embeddingGenerator); + var model = new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), definition: null, embeddingGenerator); // The embedding's .NET type (Embedding) is inferred from the embedding generator. Assert.Same(embeddingGenerator, model.VectorProperty.EmbeddingGenerator); @@ -48,7 +48,7 @@ public void Default_embedding_generator_with_clr_type_and_record_definition() ] }; - var model = new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), recordDefinition, embeddingGenerator); + var model = new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), recordDefinition, embeddingGenerator); // The embedding's .NET type (Embedding) is inferred from the embedding generator. Assert.Same(embeddingGenerator, model.VectorProperty.EmbeddingGenerator); @@ -147,7 +147,7 @@ public void Embedding_property_type_with_default_embedding_generator(bool dynami ] }, embeddingGenerator) - : new CustomModelBuilder().Build(typeof(RecordWithEmbeddingVectorProperty), definition: null, embeddingGenerator); + : new CustomModelBuilder().Build(typeof(RecordWithEmbeddingVectorProperty), typeof(int), definition: null, embeddingGenerator); var vectorProperty = model.VectorProperty; Assert.Same(embeddingGenerator, vectorProperty.EmbeddingGenerator); @@ -161,6 +161,7 @@ public void Embedding_property_type_with_property_embedding_generator() var model = new CustomModelBuilder().Build( typeof(RecordWithEmbeddingVectorProperty), + typeof(int), new VectorStoreCollectionDefinition { Properties = @@ -200,7 +201,7 @@ public void Custom_input_type(bool dynamic) var model = dynamic ? new CustomModelBuilder().BuildDynamic(recordDefinition, embeddingGenerator) - : new CustomModelBuilder().Build(typeof(RecordWithCustomerVectorProperty), recordDefinition, embeddingGenerator); + : new CustomModelBuilder().Build(typeof(RecordWithCustomerVectorProperty), typeof(int), recordDefinition, embeddingGenerator); var vectorProperty = model.VectorProperty; @@ -216,7 +217,7 @@ public void Incompatible_embedding_on_embedding_generator_throws() using var embeddingGenerator = new FakeEmbeddingGenerator>(); var exception = Assert.Throws(() => - new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), definition: null, embeddingGenerator)); + new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), definition: null, embeddingGenerator)); Assert.Equal($"Embedding generator 'FakeEmbeddingGenerator>' on vector property '{nameof(RecordWithStringVectorProperty.Embedding)}' cannot convert the input type 'string' to a supported vector type (one of: ReadOnlyMemory, Embedding, float[], ReadOnlyMemory, Embedding, Half[]).", exception.Message); } @@ -228,7 +229,7 @@ public void Incompatible_input_on_embedding_generator_throws() using var embeddingGenerator = new FakeEmbeddingGenerator>(); var exception = Assert.Throws(() => - new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), definition: null, embeddingGenerator)); + new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), definition: null, embeddingGenerator)); Assert.Equal($"Embedding generator 'FakeEmbeddingGenerator>' on vector property '{nameof(RecordWithStringVectorProperty.Embedding)}' cannot convert the input type 'string' to a supported vector type (one of: ReadOnlyMemory, Embedding, float[], ReadOnlyMemory, Embedding, Half[]).", exception.Message); } @@ -237,7 +238,7 @@ public void Incompatible_input_on_embedding_generator_throws() public void Non_embedding_vector_property_without_embedding_generator_throws() { var exception = Assert.Throws(() => - new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), definition: null, defaultEmbeddingGenerator: null)); + new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), definition: null, defaultEmbeddingGenerator: null)); Assert.Equal($"Vector property '{nameof(RecordWithStringVectorProperty.Embedding)}' has type 'string' which isn't supported by your provider, and no embedding generator is configured. Configure a generator that supports converting 'string' to vector type supported by your provider.", exception.Message); } @@ -261,7 +262,7 @@ public void EmbeddingType_not_supported_by_provider() }; var exception = Assert.Throws(() => - new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), recordDefinition, embeddingGenerator)); + new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), recordDefinition, embeddingGenerator)); Assert.Equal("Vector property 'Embedding' has embedding type 'Embedding' configured, but that type isn't supported by your provider. Supported types are ReadOnlyMemory, Embedding, float[], ReadOnlyMemory, Embedding, Half[].", exception.Message); } @@ -285,7 +286,7 @@ public void EmbeddingType_not_supported_by_generator() }; var exception = Assert.Throws(() => - new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), recordDefinition, embeddingGenerator)); + new CustomModelBuilder().Build(typeof(RecordWithStringVectorProperty), typeof(int), recordDefinition, embeddingGenerator)); Assert.Equal("Vector property 'Embedding' has embedding type 'Embedding' configured, but that type isn't supported by your embedding generator.", exception.Message); } diff --git a/dotnet/test/VectorData/Weaviate.UnitTests/WeaviateMapperTests.cs b/dotnet/test/VectorData/Weaviate.UnitTests/WeaviateMapperTests.cs index 6f26b5caa19f..4a67cfa40d9c 100644 --- a/dotnet/test/VectorData/Weaviate.UnitTests/WeaviateMapperTests.cs +++ b/dotnet/test/VectorData/Weaviate.UnitTests/WeaviateMapperTests.cs @@ -111,6 +111,7 @@ public void MapFromStorageToDataModelReturnsValidObject(bool hasNamedVectors) new WeaviateModelBuilder(hasNamedVectors) .Build( typeof(WeaviateHotel), + typeof(Guid), new VectorStoreCollectionDefinition { Properties =