diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index e2e795ecd269..05e02f82df8f 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -102,7 +102,7 @@
-
+
diff --git a/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoDynamicMapper.cs b/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoDynamicMapper.cs
index 791de1ea231c..20aa67ffdc56 100644
--- a/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoDynamicMapper.cs
+++ b/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoDynamicMapper.cs
@@ -90,7 +90,7 @@ Embedding e
{
switch (property)
{
- case KeyPropertyModel keyProperty:
+ case IKeyPropertyModel keyProperty:
if (!storageModel.TryGetValue(MongoConstants.MongoReservedKeyPropertyName, out var keyValue))
{
throw new InvalidOperationException("No key property was found in the record retrieved from storage.");
@@ -109,14 +109,14 @@ Embedding e
continue;
- case DataPropertyModel dataProperty:
+ case IDataPropertyModel dataProperty:
if (storageModel.TryGetValue(dataProperty.StorageName, out var dataValue))
{
result.Add(dataProperty.ModelName, GetDataPropertyValue(property.ModelName, property.Type, dataValue));
}
continue;
- case VectorPropertyModel vectorProperty:
+ case IVectorPropertyModel vectorProperty:
if (includeVectors && storageModel.TryGetValue(vectorProperty.StorageName, out var vectorValue))
{
result.Add(
diff --git a/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoModelBuilder.cs b/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoModelBuilder.cs
index 69f974cffd19..25b77f90839b 100644
--- a/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoModelBuilder.cs
+++ b/dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoModelBuilder.cs
@@ -27,17 +27,14 @@ internal class MongoModelBuilder() : CollectionModelBuilder(s_validationOptions)
UsesExternalSerializer = true,
};
- [RequiresUnreferencedCode("Traverses the CLR type's properties with reflection, so not compatible with trimming")]
- protected override void ProcessTypeProperties(Type type, VectorStoreCollectionDefinition? definition)
+ protected override void ProcessProperty(PropertyInfo? clrProperty, VectorStoreProperty? definitionProperty, Type? type)
{
- base.ProcessTypeProperties(type, definition);
+ base.ProcessProperty(clrProperty, definitionProperty, type);
- foreach (var property in this.Properties)
+ if (clrProperty?.GetCustomAttribute() is { } bsonElementAttribute
+ && this.PropertyMap.TryGetValue(clrProperty.Name, out var property))
{
- if (property.PropertyInfo?.GetCustomAttribute() is { } bsonElementAttribute)
- {
- property.StorageName = bsonElementAttribute.ElementName;
- }
+ property.StorageName = bsonElementAttribute.ElementName;
}
}
diff --git a/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollection.cs b/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollection.cs
index 16ed75d6c6ee..00eb8a3c485e 100644
--- a/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollection.cs
+++ b/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollection.cs
@@ -154,15 +154,15 @@ public override async Task EnsureCollectionExistsAsync(CancellationToken cancell
{
switch (property)
{
- case KeyPropertyModel p:
+ case IKeyPropertyModel p:
searchFields.Add(AzureAISearchCollectionCreateMapping.MapKeyField(p));
break;
- case DataPropertyModel p:
+ case IDataPropertyModel p:
searchFields.Add(AzureAISearchCollectionCreateMapping.MapDataField(p));
break;
- case VectorPropertyModel p:
+ case IVectorPropertyModel p:
(VectorSearchField vectorSearchField, VectorSearchAlgorithmConfiguration algorithmConfiguration, VectorSearchProfile vectorSearchProfile) = AzureAISearchCollectionCreateMapping.MapVectorField(p);
// Add the search field, plus its profile and algorithm configuration to the search config.
@@ -361,7 +361,7 @@ public override IAsyncEnumerable GetAsync(Expression?> GetSearchVectorAsync(TInput searchValue, VectorPropertyModel vectorProperty, CancellationToken cancellationToken)
+ private static async ValueTask?> GetSearchVectorAsync(TInput searchValue, IVectorPropertyModel vectorProperty, CancellationToken cancellationToken)
where TInput : notnull
=> searchValue switch
{
diff --git a/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollectionCreateMapping.cs b/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollectionCreateMapping.cs
index c21053e15dda..1ddb39fdc196 100644
--- a/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollectionCreateMapping.cs
+++ b/dotnet/src/VectorData/AzureAISearch/AzureAISearchCollectionCreateMapping.cs
@@ -18,7 +18,7 @@ internal static class AzureAISearchCollectionCreateMapping
///
/// The key property definition.
/// The for the provided property definition.
- public static SearchableField MapKeyField(KeyPropertyModel keyProperty)
+ public static SearchableField MapKeyField(IKeyPropertyModel keyProperty)
{
return new SearchableField(keyProperty.StorageName) { IsKey = true, IsFilterable = true };
}
@@ -29,7 +29,7 @@ public static SearchableField MapKeyField(KeyPropertyModel keyProperty)
/// The data property definition.
/// The for the provided property definition.
/// Throws when the definition is missing required information.
- public static SimpleField MapDataField(DataPropertyModel dataProperty)
+ public static SimpleField MapDataField(IDataPropertyModel dataProperty)
{
if (dataProperty.IsFullTextIndexed)
{
@@ -61,7 +61,7 @@ public static SimpleField MapDataField(DataPropertyModel dataProperty)
/// The vector property definition.
/// The and required index configuration.
/// Throws when the definition is missing required information, or unsupported options are configured.
- public static (VectorSearchField vectorSearchField, VectorSearchAlgorithmConfiguration algorithmConfiguration, VectorSearchProfile vectorSearchProfile) MapVectorField(VectorPropertyModel vectorProperty)
+ public static (VectorSearchField vectorSearchField, VectorSearchAlgorithmConfiguration algorithmConfiguration, VectorSearchProfile vectorSearchProfile) MapVectorField(IVectorPropertyModel vectorProperty)
{
// Build a name for the profile and algorithm configuration based on the property name
// since we'll just create a separate one for each vector property.
@@ -91,7 +91,7 @@ public static (VectorSearchField vectorSearchField, VectorSearchAlgorithmConfigu
///
/// The vector property definition.
/// The configured or default .
- public static string GetSKIndexKind(VectorPropertyModel vectorProperty)
+ public static string GetSKIndexKind(IVectorPropertyModel vectorProperty)
=> vectorProperty.IndexKind ?? IndexKind.Hnsw;
///
@@ -101,7 +101,7 @@ public static string GetSKIndexKind(VectorPropertyModel vectorProperty)
/// The vector property definition.
/// The chosen .
/// Thrown if a distance function is chosen that isn't supported by Azure AI Search.
- public static VectorSearchAlgorithmMetric GetSDKDistanceAlgorithm(VectorPropertyModel vectorProperty)
+ public static VectorSearchAlgorithmMetric GetSDKDistanceAlgorithm(IVectorPropertyModel vectorProperty)
=> vectorProperty.DistanceFunction switch
{
DistanceFunction.CosineSimilarity or null => VectorSearchAlgorithmMetric.Cosine,
diff --git a/dotnet/src/VectorData/AzureAISearch/AzureAISearchDynamicMapper.cs b/dotnet/src/VectorData/AzureAISearch/AzureAISearchDynamicMapper.cs
index 5b8024e5505d..2991add6a64d 100644
--- a/dotnet/src/VectorData/AzureAISearch/AzureAISearchDynamicMapper.cs
+++ b/dotnet/src/VectorData/AzureAISearch/AzureAISearchDynamicMapper.cs
@@ -99,7 +99,7 @@ public JsonObject MapFromDataToStorageModel(Dictionary dataMode
{
switch (property)
{
- case KeyPropertyModel keyProperty:
+ case IKeyPropertyModel keyProperty:
var key = (string?)storageModel[keyProperty.StorageName]
?? throw new InvalidOperationException($"The key property '{keyProperty.StorageName}' is missing from the record retrieved from storage.");
@@ -112,7 +112,7 @@ public JsonObject MapFromDataToStorageModel(Dictionary dataMode
continue;
- case DataPropertyModel dataProperty:
+ case IDataPropertyModel dataProperty:
{
if (storageModel.TryGetPropertyValue(dataProperty.StorageName, out var value))
{
@@ -121,7 +121,7 @@ public JsonObject MapFromDataToStorageModel(Dictionary dataMode
continue;
}
- case VectorPropertyModel vectorProperty when includeVectors:
+ case IVectorPropertyModel vectorProperty when includeVectors:
{
if (storageModel.TryGetPropertyValue(vectorProperty.StorageName, out var value))
{
@@ -147,7 +147,7 @@ public JsonObject MapFromDataToStorageModel(Dictionary dataMode
continue;
}
- case VectorPropertyModel vectorProperty when !includeVectors:
+ case IVectorPropertyModel vectorProperty when !includeVectors:
break;
default:
diff --git a/dotnet/src/VectorData/Common/SqlFilterTranslator.cs b/dotnet/src/VectorData/Common/SqlFilterTranslator.cs
index 0652d7675049..a2af5d775169 100644
--- a/dotnet/src/VectorData/Common/SqlFilterTranslator.cs
+++ b/dotnet/src/VectorData/Common/SqlFilterTranslator.cs
@@ -196,7 +196,7 @@ private void TranslateMember(MemberExpression memberExpression, bool isSearchCon
throw new NotSupportedException($"Member access for '{memberExpression.Member.Name}' is unsupported - only member access over the filter parameter are supported");
}
- protected virtual void GenerateColumn(PropertyModel property, bool isSearchCondition = false)
+ protected virtual void GenerateColumn(IPropertyModel property, bool isSearchCondition = false)
// StorageName is considered to be a safe input, we quote and escape it mostly to produce valid SQL.
=> this._sql.Append('"').Append(property.StorageName.Replace("\"", "\"\"")).Append('"');
@@ -332,7 +332,7 @@ private void TranslateAny(Expression source, LambdaExpression lambda)
}
}
- protected abstract void TranslateAnyContainsOverArrayColumn(PropertyModel property, object? values);
+ protected abstract void TranslateAnyContainsOverArrayColumn(IPropertyModel property, object? values);
private void TranslateUnary(UnaryExpression unary, bool isSearchCondition)
{
diff --git a/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoCollectionCreateMapping.cs b/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoCollectionCreateMapping.cs
index 08ab4c859e7e..14f3944356b8 100644
--- a/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoCollectionCreateMapping.cs
+++ b/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoCollectionCreateMapping.cs
@@ -21,7 +21,7 @@ internal static class CosmosMongoCollectionCreateMapping
/// Number of clusters that the inverted file (IVF) index uses to group the vector data.
/// The size of the dynamic candidate list for constructing the graph.
public static BsonArray GetVectorIndexes(
- IReadOnlyList vectorProperties,
+ IReadOnlyList vectorProperties,
HashSet uniqueIndexes,
int numLists,
int efConstruction)
@@ -71,7 +71,7 @@ public static BsonArray GetVectorIndexes(
/// Collection of data properties for index creation.
/// Collection of unique existing indexes to avoid creating duplicates.
public static BsonArray GetFilterableDataIndexes(
- IReadOnlyList dataProperties,
+ IReadOnlyList dataProperties,
HashSet uniqueIndexes)
{
var indexArray = new BsonArray();
diff --git a/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoFilterTranslator.cs b/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoFilterTranslator.cs
index 5fca84cbe3d3..dbd99d06d6f4 100644
--- a/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoFilterTranslator.cs
+++ b/dotnet/src/VectorData/CosmosMongoDB/CosmosMongoFilterTranslator.cs
@@ -62,7 +62,7 @@ private BsonDocument TranslateEqualityComparison(BinaryExpression binary)
? this.GenerateEqualityComparison(property, leftConstant, binary.NodeType)
: throw new NotSupportedException("Invalid equality/comparison");
- private BsonDocument GenerateEqualityComparison(PropertyModel property, object? value, ExpressionType nodeType)
+ private BsonDocument GenerateEqualityComparison(IPropertyModel property, object? value, ExpressionType nodeType)
{
if (value is null)
{
diff --git a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlCollection.cs b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlCollection.cs
index d535a16e6d31..fee83a1c5b5b 100644
--- a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlCollection.cs
+++ b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlCollection.cs
@@ -56,7 +56,7 @@ public class CosmosNoSqlCollection : VectorStoreCollectionThe properties to use as partition key (supports hierarchical partition keys up to 3 levels).
- private readonly List _partitionKeyProperties;
+ private readonly List _partitionKeyProperties;
/// The mapper to use when mapping between the consumer data model and the Azure CosmosDB NoSQL record.
private readonly ICosmosNoSqlMapper _mapper;
@@ -181,7 +181,7 @@ internal CosmosNoSqlCollection(
throw new ArgumentException("Cosmos DB supports at most 3 levels of hierarchical partition keys.");
}
- this._partitionKeyProperties = new List(options.PartitionKeyProperties.Count);
+ this._partitionKeyProperties = new List(options.PartitionKeyProperties.Count);
foreach (var propertyName in options.PartitionKeyProperties)
{
@@ -563,7 +563,7 @@ public override async IAsyncEnumerable> SearchAsync<
}
}
- private static async ValueTask GetSearchVectorAsync(TInput searchValue, VectorPropertyModel vectorProperty, CancellationToken cancellationToken)
+ private static async ValueTask GetSearchVectorAsync(TInput searchValue, IVectorPropertyModel vectorProperty, CancellationToken cancellationToken)
where TInput : notnull
=> searchValue switch
{
diff --git a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlCollectionQueryBuilder.cs b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlCollectionQueryBuilder.cs
index 0a07992b582b..fba7248a67b6 100644
--- a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlCollectionQueryBuilder.cs
+++ b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlCollectionQueryBuilder.cs
@@ -40,10 +40,10 @@ public static QueryDefinition BuildSearchQuery(
var tableVariableName = CosmosNoSqlConstants.ContainerAlias;
- IEnumerable projectionProperties = model.Properties;
+ IEnumerable projectionProperties = model.Properties;
if (!includeVectors)
{
- projectionProperties = projectionProperties.Where(p => p is not VectorPropertyModel);
+ projectionProperties = projectionProperties.Where(p => p is not IVectorPropertyModel);
}
var fieldsArgument = projectionProperties.Select(p => GeneratePropertyAccess(tableVariableName, p.StorageName));
var vectorDistanceArgument = $"VectorDistance({GeneratePropertyAccess(tableVariableName, vectorPropertyName)}, {VectorVariableName})";
@@ -167,10 +167,10 @@ internal static QueryDefinition BuildSearchQuery(
{
var tableVariableName = CosmosNoSqlConstants.ContainerAlias;
- IEnumerable projectionProperties = model.Properties;
+ IEnumerable projectionProperties = model.Properties;
if (!filterOptions.IncludeVectors)
{
- projectionProperties = projectionProperties.Where(p => p is not VectorPropertyModel);
+ projectionProperties = projectionProperties.Where(p => p is not IVectorPropertyModel);
}
var fieldsArgument = projectionProperties.Select(field => GeneratePropertyAccess(tableVariableName, field.StorageName));
diff --git a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlDynamicMapper.cs b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlDynamicMapper.cs
index 31720476142a..641a86743ba6 100644
--- a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlDynamicMapper.cs
+++ b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlDynamicMapper.cs
@@ -123,7 +123,7 @@ static bool TryGetReadOnlyMemory(object value, [NotNullWhen(true)] out ReadOn
{
switch (property)
{
- case KeyPropertyModel keyProperty:
+ case IKeyPropertyModel keyProperty:
var key = (string?)storageModel[CosmosNoSqlConstants.ReservedKeyPropertyName]
?? throw new InvalidOperationException($"The key property '{keyProperty.StorageName}' is missing from the record retrieved from storage.");
@@ -136,14 +136,14 @@ static bool TryGetReadOnlyMemory(object value, [NotNullWhen(true)] out ReadOn
continue;
- case DataPropertyModel dataProperty:
+ case IDataPropertyModel dataProperty:
if (storageModel.TryGetPropertyValue(dataProperty.StorageName, out var dataValue))
{
result.Add(property.ModelName, dataValue.Deserialize(property.Type, jsonSerializerOptions));
}
continue;
- case VectorPropertyModel vectorProperty:
+ case IVectorPropertyModel vectorProperty:
if (includeVectors && storageModel.TryGetPropertyValue(vectorProperty.StorageName, out var vectorValue))
{
if (vectorValue is not null)
diff --git a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlFilterTranslator.cs b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlFilterTranslator.cs
index 63f173a8cf45..b6ebb358b7e2 100644
--- a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlFilterTranslator.cs
+++ b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlFilterTranslator.cs
@@ -300,7 +300,7 @@ private void TranslateAny(Expression source, LambdaExpression lambda)
}
}
- private void GenerateAnyContains(PropertyModel property, object? values)
+ private void GenerateAnyContains(IPropertyModel property, object? values)
{
this._sql.Append("EXISTS(SELECT VALUE t FROM t IN ");
this.GeneratePropertyAccess(property);
@@ -309,7 +309,7 @@ private void GenerateAnyContains(PropertyModel property, object? values)
this._sql.Append(", t))");
}
- private void GenerateAnyContains(PropertyModel property, QueryParameterExpression queryParameter)
+ private void GenerateAnyContains(IPropertyModel property, QueryParameterExpression queryParameter)
{
this._sql.Append("EXISTS(SELECT VALUE t FROM t IN ");
this.GeneratePropertyAccess(property);
@@ -361,7 +361,7 @@ protected void TranslateQueryParameter(string name, object? value)
this._sql.Append(name);
}
- protected virtual void GeneratePropertyAccess(PropertyModel property)
+ protected virtual void GeneratePropertyAccess(IPropertyModel property)
=> this._sql
.Append(CosmosNoSqlConstants.ContainerAlias)
.Append("[\"")
diff --git a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlMapper.cs b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlMapper.cs
index 8a8644b80198..fdf20e1f74ab 100644
--- a/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlMapper.cs
+++ b/dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlMapper.cs
@@ -19,7 +19,7 @@ internal sealed class CosmosNoSqlMapper : ICosmosNoSqlMapper
where TRecord : class
{
private readonly CollectionModel _model;
- private readonly KeyPropertyModel _keyProperty;
+ private readonly IKeyPropertyModel _keyProperty;
private readonly JsonSerializerOptions _jsonSerializerOptions;
public CosmosNoSqlMapper(CollectionModel model, JsonSerializerOptions? jsonSerializerOptions)
@@ -41,8 +41,8 @@ public JsonObject MapFromDataToStorageModel(TRecord dataModel, int recordIndex,
// The key property in Azure CosmosDB NoSQL is always named 'id'.
// But the external JSON serializer used just above isn't aware of that, and will produce a JSON object with another name, taking into
- // account e.g. naming policies. TemporaryStorageName gets populated in the model builder - containing that name - once VectorStoreModelBuildingOptions.ReservedKeyPropertyName is set
- RenameJsonProperty(jsonObject, this._keyProperty.TemporaryStorageName!, CosmosNoSqlConstants.ReservedKeyPropertyName);
+ // account e.g. naming policies. SerializedKeyName gets populated in the model builder - containing that name - once VectorStoreModelBuildingOptions.ReservedKeyPropertyName is set
+ RenameJsonProperty(jsonObject, this._keyProperty.SerializedKeyName!, CosmosNoSqlConstants.ReservedKeyPropertyName);
// Go over the vector properties; inject any generated embeddings to overwrite the JSON serialized above.
// Also, for Embedding properties we also need to overwrite with a simple array (since Embedding gets serialized as a complex object).
@@ -116,7 +116,7 @@ public JsonObject MapFromDataToStorageModel(TRecord dataModel, int recordIndex,
public TRecord MapFromStorageToDataModel(JsonObject storageModel, bool includeVectors)
{
// See above comment.
- RenameJsonProperty(storageModel, CosmosNoSqlConstants.ReservedKeyPropertyName, this._keyProperty.TemporaryStorageName!);
+ RenameJsonProperty(storageModel, CosmosNoSqlConstants.ReservedKeyPropertyName, this._keyProperty.SerializedKeyName!);
foreach (var vectorProperty in this._model.VectorProperties)
{
diff --git a/dotnet/src/VectorData/MongoDB/MongoCollection.cs b/dotnet/src/VectorData/MongoDB/MongoCollection.cs
index 7cb8f234ec16..feff16d46873 100644
--- a/dotnet/src/VectorData/MongoDB/MongoCollection.cs
+++ b/dotnet/src/VectorData/MongoDB/MongoCollection.cs
@@ -426,7 +426,7 @@ public override async IAsyncEnumerable> SearchAsync<
}
}
- private static async ValueTask GetSearchVectorArrayAsync(TInput searchValue, VectorPropertyModel vectorProperty, CancellationToken cancellationToken)
+ private static async ValueTask GetSearchVectorArrayAsync(TInput searchValue, IVectorPropertyModel vectorProperty, CancellationToken cancellationToken)
where TInput : notnull
{
if (searchValue is float[] array)
diff --git a/dotnet/src/VectorData/MongoDB/MongoCollectionCreateMapping.cs b/dotnet/src/VectorData/MongoDB/MongoCollectionCreateMapping.cs
index 31fa5bd7ba4d..c16950464462 100644
--- a/dotnet/src/VectorData/MongoDB/MongoCollectionCreateMapping.cs
+++ b/dotnet/src/VectorData/MongoDB/MongoCollectionCreateMapping.cs
@@ -17,7 +17,7 @@ internal static class MongoCollectionCreateMapping
/// Returns an array of indexes to create for vector properties.
///
/// Collection of vector properties for index creation.
- public static BsonArray GetVectorIndexFields(IReadOnlyList vectorProperties)
+ public static BsonArray GetVectorIndexFields(IReadOnlyList vectorProperties)
{
var indexArray = new BsonArray();
@@ -42,7 +42,7 @@ public static BsonArray GetVectorIndexFields(IReadOnlyList
/// Returns an array of indexes to create for filterable data properties.
///
/// Collection of data properties for index creation.
- public static BsonArray GetFilterableDataIndexFields(IReadOnlyList dataProperties)
+ public static BsonArray GetFilterableDataIndexFields(IReadOnlyList dataProperties)
{
var indexArray = new BsonArray();
@@ -68,7 +68,7 @@ public static BsonArray GetFilterableDataIndexFields(IReadOnlyList
/// Collection of data properties for index creation.
- public static List GetFullTextSearchableDataIndexFields(IReadOnlyList dataProperties)
+ public static List GetFullTextSearchableDataIndexFields(IReadOnlyList dataProperties)
{
var fieldElements = new List();
diff --git a/dotnet/src/VectorData/MongoDB/MongoFilterTranslator.cs b/dotnet/src/VectorData/MongoDB/MongoFilterTranslator.cs
index 9f32e3d0f4be..d2d08137c742 100644
--- a/dotnet/src/VectorData/MongoDB/MongoFilterTranslator.cs
+++ b/dotnet/src/VectorData/MongoDB/MongoFilterTranslator.cs
@@ -62,7 +62,7 @@ private BsonDocument TranslateEqualityComparison(BinaryExpression binary)
? this.GenerateEqualityComparison(property, leftConstant, binary.NodeType)
: throw new NotSupportedException("Invalid equality/comparison");
- private BsonDocument GenerateEqualityComparison(PropertyModel property, object? value, ExpressionType nodeType)
+ private BsonDocument GenerateEqualityComparison(IPropertyModel property, object? value, ExpressionType nodeType)
{
if (value is null)
{
diff --git a/dotnet/src/VectorData/PgVector/PostgresCollection.cs b/dotnet/src/VectorData/PgVector/PostgresCollection.cs
index 79d47b7f794c..1142f7e2549b 100644
--- a/dotnet/src/VectorData/PgVector/PostgresCollection.cs
+++ b/dotnet/src/VectorData/PgVector/PostgresCollection.cs
@@ -179,7 +179,7 @@ public override async Task UpsertAsync(IEnumerable records, Cancellatio
IReadOnlyList? recordsList = null;
// If an embedding generator is defined, invoke it once per property for all records.
- Dictionary>? generatedEmbeddings = null;
+ Dictionary>? generatedEmbeddings = null;
var vectorPropertyCount = this._model.VectorProperties.Count;
for (var i = 0; i < vectorPropertyCount; i++)
@@ -209,7 +209,7 @@ public override async Task UpsertAsync(IEnumerable records, Cancellatio
// TODO: Ideally we'd group together vector properties using the same generator (and with the same input and output properties),
// and generate embeddings for them in a single batch. That's some more complexity though.
- generatedEmbeddings ??= new Dictionary>(vectorPropertyCount);
+ generatedEmbeddings ??= new Dictionary>(vectorPropertyCount);
generatedEmbeddings[vectorProperty] = await vectorProperty.GenerateEmbeddingsAsync(records.Select(r => vectorProperty.GetValueAsObject(r)), cancellationToken).ConfigureAwait(false);
}
@@ -554,7 +554,7 @@ private Task RunOperationAsync(string operationName, Func> operati
///
/// Converts a search input value to a PostgreSQL vector representation, generating embeddings if necessary.
///
- private async Task ConvertSearchInputToVectorAsync(TInput searchValue, VectorPropertyModel vectorProperty, CancellationToken cancellationToken)
+ private async Task ConvertSearchInputToVectorAsync(TInput searchValue, IVectorPropertyModel vectorProperty, CancellationToken cancellationToken)
where TInput : notnull
{
object vector = searchValue switch
diff --git a/dotnet/src/VectorData/PgVector/PostgresFilterTranslator.cs b/dotnet/src/VectorData/PgVector/PostgresFilterTranslator.cs
index eb6d38f9ddb8..9a4e495f0488 100644
--- a/dotnet/src/VectorData/PgVector/PostgresFilterTranslator.cs
+++ b/dotnet/src/VectorData/PgVector/PostgresFilterTranslator.cs
@@ -105,7 +105,7 @@ protected override void TranslateContainsOverParameterizedArray(Expression sourc
this._sql.Append(')');
}
- protected override void TranslateAnyContainsOverArrayColumn(PropertyModel property, object? values)
+ protected override void TranslateAnyContainsOverArrayColumn(IPropertyModel property, object? values)
{
// Translate r.Strings.Any(s => array.Contains(s)) to: column && ARRAY[values]
// The && operator checks if the two arrays have any elements in common
diff --git a/dotnet/src/VectorData/PgVector/PostgresMapper.cs b/dotnet/src/VectorData/PgVector/PostgresMapper.cs
index 57d4c2b56a18..4b13b67c3399 100644
--- a/dotnet/src/VectorData/PgVector/PostgresMapper.cs
+++ b/dotnet/src/VectorData/PgVector/PostgresMapper.cs
@@ -103,7 +103,7 @@ public TRecord MapFromStorageToDataModel(NpgsqlDataReader reader, bool includeVe
return record;
}
- private static void PopulateProperty(PropertyModel property, NpgsqlDataReader reader, TRecord record)
+ private static void PopulateProperty(IPropertyModel property, NpgsqlDataReader reader, TRecord record)
{
int ordinal = reader.GetOrdinal(property.StorageName);
diff --git a/dotnet/src/VectorData/PgVector/PostgresPropertyExtensions.cs b/dotnet/src/VectorData/PgVector/PostgresPropertyExtensions.cs
index 1ef1ef9071d7..5893a6026624 100644
--- a/dotnet/src/VectorData/PgVector/PostgresPropertyExtensions.cs
+++ b/dotnet/src/VectorData/PgVector/PostgresPropertyExtensions.cs
@@ -50,7 +50,7 @@ public static VectorStoreDataProperty WithFullTextSearchLanguage(this VectorStor
///
/// The data property model to read from.
/// The configured language, or the default language ("english") if not set.
- internal static string GetFullTextSearchLanguageOrDefault(this DataPropertyModel property)
+ internal static string GetFullTextSearchLanguageOrDefault(this IDataPropertyModel property)
=> property.ProviderAnnotations?.TryGetValue(FullTextSearchLanguageKey, out var value) == true && value is string language
? language
: PostgresConstants.DefaultFullTextSearchLanguage;
@@ -109,7 +109,7 @@ public static TProperty WithStoreType(this TProperty property, string
///
/// Gets whether the property model has been configured with a timestamp (without time zone) store type.
///
- internal static bool IsTimestampWithoutTimezone(this PropertyModel property)
+ internal static bool IsTimestampWithoutTimezone(this IPropertyModel property)
=> property.ProviderAnnotations?.TryGetValue(StoreTypeKey, out var value) == true
&& value is string storeType
&& IsTimestampStoreType(storeType);
diff --git a/dotnet/src/VectorData/PgVector/PostgresPropertyMapping.cs b/dotnet/src/VectorData/PgVector/PostgresPropertyMapping.cs
index 15e872ec70b2..426517583f39 100644
--- a/dotnet/src/VectorData/PgVector/PostgresPropertyMapping.cs
+++ b/dotnet/src/VectorData/PgVector/PostgresPropertyMapping.cs
@@ -41,7 +41,7 @@ internal static class PostgresPropertyMapping
///
/// Gets the NpgsqlDbType for a property, taking into account any store type annotation.
///
- internal static NpgsqlDbType? GetNpgsqlDbType(PropertyModel property)
+ internal static NpgsqlDbType? GetNpgsqlDbType(IPropertyModel property)
=> (Nullable.GetUnderlyingType(property.Type) ?? property.Type) switch
{
Type t when t == typeof(bool) => NpgsqlDbType.Boolean,
@@ -72,7 +72,7 @@ internal static class PostgresPropertyMapping
///
/// Maps a .NET type to a PostgreSQL type name, taking into account any store type annotation on the property.
///
- internal static (string PgType, bool IsNullable) GetPostgresTypeName(PropertyModel property)
+ internal static (string PgType, bool IsNullable) GetPostgresTypeName(IPropertyModel property)
{
static bool TryGetBaseType(Type type, [NotNullWhen(true)] out string? typeName)
{
@@ -142,7 +142,7 @@ static bool TryGetBaseType(Type type, [NotNullWhen(true)] out string? typeName)
///
/// The vector property.
/// The PostgreSQL vector type name.
- public static (string PgType, bool IsNullable) GetPgVectorTypeName(VectorPropertyModel vectorProperty)
+ public static (string PgType, bool IsNullable) GetPgVectorTypeName(IVectorPropertyModel vectorProperty)
{
var unwrappedEmbeddingType = Nullable.GetUnderlyingType(vectorProperty.EmbeddingType) ?? vectorProperty.EmbeddingType;
@@ -181,18 +181,18 @@ public static NpgsqlParameter GetNpgsqlParameter(object? value)
///
/// The default index kind is "Flat", which prevents the creation of an index.
///
- public static List<(string column, string kind, string function, bool isVector, bool isFullText, string? fullTextLanguage)> GetIndexInfo(IReadOnlyList properties)
+ public static List<(string column, string kind, string function, bool isVector, bool isFullText, string? fullTextLanguage)> GetIndexInfo(IReadOnlyList properties)
{
var vectorIndexesToCreate = new List<(string column, string kind, string function, bool isVector, bool isFullText, string? fullTextLanguage)>();
foreach (var property in properties)
{
switch (property)
{
- case KeyPropertyModel:
+ case IKeyPropertyModel:
// There is no need to create a separate index for the key property.
break;
- case VectorPropertyModel vectorProperty:
+ case IVectorPropertyModel vectorProperty:
var indexKind = vectorProperty.IndexKind ?? PostgresConstants.DefaultIndexKind;
var distanceFunction = vectorProperty.DistanceFunction ?? PostgresConstants.DefaultDistanceFunction;
@@ -215,7 +215,7 @@ public static NpgsqlParameter GetNpgsqlParameter(object? value)
break;
- case DataPropertyModel dataProperty:
+ case IDataPropertyModel dataProperty:
if (dataProperty.IsIndexed)
{
vectorIndexesToCreate.Add((dataProperty.StorageName, kind: "", function: "", isVector: false, isFullText: false, fullTextLanguage: null));
diff --git a/dotnet/src/VectorData/PgVector/PostgresSqlBuilder.cs b/dotnet/src/VectorData/PgVector/PostgresSqlBuilder.cs
index c5374b053d78..317b99707bba 100644
--- a/dotnet/src/VectorData/PgVector/PostgresSqlBuilder.cs
+++ b/dotnet/src/VectorData/PgVector/PostgresSqlBuilder.cs
@@ -189,7 +189,7 @@ internal static bool BuildUpsertCommand(
string tableName,
CollectionModel model,
IEnumerable records,
- Dictionary>? generatedEmbeddings)
+ Dictionary>? generatedEmbeddings)
{
// Note: since keys may be auto-generated, we can't use a single multi-value INSERT statement, since that would return
// the generated keys in random order. Use a batch of single-value INSERT statements instead.
@@ -209,14 +209,14 @@ internal static bool BuildUpsertCommand(
foreach (var property in model.Properties)
{
- if (property is KeyPropertyModel && autoGeneratedKey)
+ if (property is IKeyPropertyModel && autoGeneratedKey)
{
continue;
}
var value = property.GetValueAsObject(record);
- if (property is VectorPropertyModel vectorProperty)
+ if (property is IVectorPropertyModel vectorProperty)
{
if (generatedEmbeddings?[vectorProperty] is IReadOnlyList ge)
{
@@ -268,7 +268,7 @@ string GenerateSingleUpsertSql(bool autoGeneratedKey)
var i = 0;
foreach (var property in model.Properties)
{
- if (property is KeyPropertyModel && autoGeneratedKey)
+ if (property is IKeyPropertyModel && autoGeneratedKey)
{
continue;
}
@@ -288,7 +288,7 @@ string GenerateSingleUpsertSql(bool autoGeneratedKey)
i = 0;
foreach (var property in model.Properties)
{
- if (property is KeyPropertyModel && autoGeneratedKey)
+ if (property is IKeyPropertyModel && autoGeneratedKey)
{
continue;
}
@@ -322,7 +322,7 @@ string GenerateSingleUpsertSql(bool autoGeneratedKey)
i = 0;
foreach (var property in model.Properties)
{
- if (property is KeyPropertyModel)
+ if (property is IKeyPropertyModel)
{
continue;
}
@@ -380,7 +380,7 @@ internal static void BuildGetBatchCommand(NpgsqlCommand command, string? s
var first = true;
foreach (var property in model.Properties)
{
- if (!includeVectors && property is VectorPropertyModel)
+ if (!includeVectors && property is IVectorPropertyModel)
{
continue;
}
@@ -418,7 +418,7 @@ internal static void BuildDeleteCommand(NpgsqlCommand command, string? sch
}
///
- internal static void BuildDeleteBatchCommand(NpgsqlCommand command, string? schema, string tableName, KeyPropertyModel keyProperty, List keys)
+ internal static void BuildDeleteBatchCommand(NpgsqlCommand command, string? schema, string tableName, IKeyPropertyModel keyProperty, List keys)
{
NpgsqlDbType? keyType = PostgresPropertyMapping.GetNpgsqlDbType(keyProperty) ?? throw new ArgumentException($"Unsupported key type {typeof(TKey).Name}");
@@ -491,7 +491,7 @@ private static (string Condition, List Parameters) GenerateFilterConditi
///
internal static void BuildGetNearestMatchCommand(
- NpgsqlCommand command, string? schema, string tableName, CollectionModel model, VectorPropertyModel vectorProperty, object vectorValue,
+ NpgsqlCommand command, string? schema, string tableName, CollectionModel model, IVectorPropertyModel vectorProperty, object vectorValue,
Expression>? filter, int? skip, bool includeVectors, int limit,
double? scoreThreshold = null)
{
@@ -603,7 +603,7 @@ internal static void BuildSelectWhereCommand(
var first = true;
foreach (var property in model.Properties)
{
- if (options.IncludeVectors || property is not VectorPropertyModel)
+ if (options.IncludeVectors || property is not IVectorPropertyModel)
{
if (!first)
{
@@ -683,7 +683,7 @@ private static StringBuilder AppendTableName(this StringBuilder sb, string? sche
///
internal static void BuildHybridSearchCommand(
NpgsqlCommand command, string? schema, string tableName, CollectionModel model,
- VectorPropertyModel vectorProperty, DataPropertyModel textProperty,
+ IVectorPropertyModel vectorProperty, IDataPropertyModel textProperty,
object vectorValue, ICollection keywords,
Expression>? filter,
int? skip, bool includeVectors, int top, double? scoreThreshold = null)
@@ -695,7 +695,7 @@ internal static void BuildHybridSearchCommand(
StringBuilder columns = new();
for (var i = 0; i < model.Properties.Count; i++)
{
- if (!includeVectors && model.Properties[i] is VectorPropertyModel)
+ if (!includeVectors && model.Properties[i] is IVectorPropertyModel)
{
continue;
}
diff --git a/dotnet/src/VectorData/Pinecone/PineconeCollection.cs b/dotnet/src/VectorData/Pinecone/PineconeCollection.cs
index 66a303936849..4192d3dd2ca6 100644
--- a/dotnet/src/VectorData/Pinecone/PineconeCollection.cs
+++ b/dotnet/src/VectorData/Pinecone/PineconeCollection.cs
@@ -596,7 +596,7 @@ private static ServerlessSpecCloud MapCloud(string serverlessIndexCloud)
_ => throw new ArgumentException($"Invalid serverless index cloud: {serverlessIndexCloud}.", nameof(serverlessIndexCloud))
};
- private static CreateIndexRequestMetric MapDistanceFunction(VectorPropertyModel vectorProperty)
+ private static CreateIndexRequestMetric MapDistanceFunction(IVectorPropertyModel vectorProperty)
=> vectorProperty.DistanceFunction switch
{
DistanceFunction.CosineSimilarity or null => CreateIndexRequestMetric.Cosine,
diff --git a/dotnet/src/VectorData/Pinecone/PineconeFilterTranslator.cs b/dotnet/src/VectorData/Pinecone/PineconeFilterTranslator.cs
index 0fe0875e0b81..d0012d451de9 100644
--- a/dotnet/src/VectorData/Pinecone/PineconeFilterTranslator.cs
+++ b/dotnet/src/VectorData/Pinecone/PineconeFilterTranslator.cs
@@ -71,7 +71,7 @@ private Metadata TranslateEqualityComparison(BinaryExpression binary)
? this.GenerateEqualityComparison(property, leftConstant, binary.NodeType)
: throw new NotSupportedException("Invalid equality/comparison");
- private Metadata GenerateEqualityComparison(PropertyModel property, object? value, ExpressionType nodeType)
+ private Metadata GenerateEqualityComparison(IPropertyModel property, object? value, ExpressionType nodeType)
{
if (value is null)
{
diff --git a/dotnet/src/VectorData/Qdrant/QdrantCollection.cs b/dotnet/src/VectorData/Qdrant/QdrantCollection.cs
index d37f90a60d76..7e9390379539 100644
--- a/dotnet/src/VectorData/Qdrant/QdrantCollection.cs
+++ b/dotnet/src/VectorData/Qdrant/QdrantCollection.cs
@@ -580,7 +580,7 @@ public override async IAsyncEnumerable> SearchAsync<
}
}
- private static async ValueTask GetSearchVectorArrayAsync(TInput searchValue, VectorPropertyModel vectorProperty, CancellationToken cancellationToken)
+ private static async ValueTask GetSearchVectorArrayAsync(TInput searchValue, IVectorPropertyModel vectorProperty, CancellationToken cancellationToken)
where TInput : notnull
{
if (searchValue is float[] array)
diff --git a/dotnet/src/VectorData/Qdrant/QdrantCollectionCreateMapping.cs b/dotnet/src/VectorData/Qdrant/QdrantCollectionCreateMapping.cs
index c03145ce8fb5..3840d8ea5c80 100644
--- a/dotnet/src/VectorData/Qdrant/QdrantCollectionCreateMapping.cs
+++ b/dotnet/src/VectorData/Qdrant/QdrantCollectionCreateMapping.cs
@@ -61,7 +61,7 @@ internal static class QdrantCollectionCreateMapping
/// The property to map.
/// The mapped .
/// Thrown if the property is missing information or has unsupported options specified.
- public static VectorParams MapSingleVector(VectorPropertyModel vectorProperty)
+ public static VectorParams MapSingleVector(IVectorPropertyModel vectorProperty)
{
if (vectorProperty!.IndexKind is not null and not IndexKind.Hnsw)
{
@@ -77,7 +77,7 @@ public static VectorParams MapSingleVector(VectorPropertyModel vectorProperty)
/// The properties to map.
/// THe mapped .
/// Thrown if the property is missing information or has unsupported options specified.
- public static VectorParamsMap MapNamedVectors(IEnumerable vectorProperties)
+ public static VectorParamsMap MapNamedVectors(IEnumerable vectorProperties)
{
var vectorParamsMap = new VectorParamsMap();
@@ -97,7 +97,7 @@ public static VectorParamsMap MapNamedVectors(IEnumerable v
/// The vector property definition.
/// The chosen .
/// Thrown if a distance function is chosen that isn't supported by qdrant.
- public static Distance GetSDKDistanceAlgorithm(VectorPropertyModel vectorProperty)
+ public static Distance GetSDKDistanceAlgorithm(IVectorPropertyModel vectorProperty)
=> vectorProperty.DistanceFunction switch
{
DistanceFunction.CosineSimilarity or null => Distance.Cosine,
diff --git a/dotnet/src/VectorData/Qdrant/QdrantMapper.cs b/dotnet/src/VectorData/Qdrant/QdrantMapper.cs
index 992b5fa05891..991c85458aef 100644
--- a/dotnet/src/VectorData/Qdrant/QdrantMapper.cs
+++ b/dotnet/src/VectorData/Qdrant/QdrantMapper.cs
@@ -86,7 +86,7 @@ generatedEmbeddings is null
return pointStruct;
- Vector GetVector(PropertyModel property, object? embedding)
+ Vector GetVector(IPropertyModel property, object? embedding)
=> embedding switch
{
ReadOnlyMemory m => m.ToArray(),
@@ -128,7 +128,7 @@ public TRecord MapFromStorageToDataModel(PointId pointId, MapField data = value switch
{
diff --git a/dotnet/src/VectorData/Redis/RedisCollectionCreateMapping.cs b/dotnet/src/VectorData/Redis/RedisCollectionCreateMapping.cs
index f276e8a3a429..abedad8a8dfb 100644
--- a/dotnet/src/VectorData/Redis/RedisCollectionCreateMapping.cs
+++ b/dotnet/src/VectorData/Redis/RedisCollectionCreateMapping.cs
@@ -51,7 +51,7 @@ internal static class RedisCollectionCreateMapping
/// A value indicating whether to include $. prefix for field names as required in JSON mode.
/// The mapped Redis .
/// Thrown if there are missing required or unsupported configuration options set.
- public static Schema MapToSchema(IEnumerable properties, bool useDollarPrefix)
+ public static Schema MapToSchema(IEnumerable properties, bool useDollarPrefix)
{
var schema = new Schema();
var fieldNamePrefix = useDollarPrefix ? "$." : string.Empty;
@@ -63,11 +63,11 @@ public static Schema MapToSchema(IEnumerable properties, bool use
switch (property)
{
- case KeyPropertyModel keyProperty:
+ case IKeyPropertyModel keyProperty:
// Do nothing, since key is not stored as part of the payload and therefore doesn't have to be added to the index.
continue;
- case DataPropertyModel dataProperty when dataProperty.IsIndexed || dataProperty.IsFullTextIndexed:
+ case IDataPropertyModel dataProperty when dataProperty.IsIndexed || dataProperty.IsFullTextIndexed:
if (dataProperty.IsIndexed && dataProperty.IsFullTextIndexed)
{
throw new InvalidOperationException($"Property '{dataProperty.ModelName}' has both {nameof(VectorStoreDataProperty.IsIndexed)} and {nameof(VectorStoreDataProperty.IsFullTextIndexed)} set to true, and this is not supported by the Redis VectorStore.");
@@ -109,7 +109,7 @@ public static Schema MapToSchema(IEnumerable properties, bool use
continue;
- case VectorPropertyModel vectorProperty:
+ case IVectorPropertyModel vectorProperty:
var indexKind = GetSDKIndexKind(vectorProperty);
var vectorType = GetSDKVectorType(vectorProperty);
var dimensions = vectorProperty.Dimensions.ToString(CultureInfo.InvariantCulture);
@@ -139,7 +139,7 @@ static bool IsTagsType(Type type)
/// The vector property definition.
/// The chosen .
/// Thrown if a index type was chosen that isn't supported by Redis.
- public static Schema.VectorField.VectorAlgo GetSDKIndexKind(VectorPropertyModel vectorProperty)
+ public static Schema.VectorField.VectorAlgo GetSDKIndexKind(IVectorPropertyModel vectorProperty)
=> vectorProperty.IndexKind switch
{
IndexKind.Hnsw or null => Schema.VectorField.VectorAlgo.HNSW,
@@ -154,7 +154,7 @@ public static Schema.VectorField.VectorAlgo GetSDKIndexKind(VectorPropertyModel
/// The vector property definition.
/// The chosen distance metric.
/// Thrown if a distance function is chosen that isn't supported by Redis.
- public static string GetSDKDistanceAlgorithm(VectorPropertyModel vectorProperty)
+ public static string GetSDKDistanceAlgorithm(IVectorPropertyModel vectorProperty)
=> vectorProperty.DistanceFunction switch
{
DistanceFunction.CosineSimilarity or null => "COSINE",
@@ -171,7 +171,7 @@ public static string GetSDKDistanceAlgorithm(VectorPropertyModel vectorProperty)
/// The vector property definition.
/// The SDK required vector type.
/// Thrown if the property data type is not supported by the connector.
- public static string GetSDKVectorType(VectorPropertyModel vectorProperty)
+ public static string GetSDKVectorType(IVectorPropertyModel vectorProperty)
=> (Nullable.GetUnderlyingType(vectorProperty.EmbeddingType) ?? vectorProperty.EmbeddingType) switch
{
Type t when t == typeof(ReadOnlyMemory) => "FLOAT32",
diff --git a/dotnet/src/VectorData/Redis/RedisCollectionSearchMapping.cs b/dotnet/src/VectorData/Redis/RedisCollectionSearchMapping.cs
index e9c36fd87605..d037fe5effbd 100644
--- a/dotnet/src/VectorData/Redis/RedisCollectionSearchMapping.cs
+++ b/dotnet/src/VectorData/Redis/RedisCollectionSearchMapping.cs
@@ -47,7 +47,7 @@ public static byte[] ValidateVectorAndConvertToBytes(TVector vector, st
/// The vector property.
/// The set of fields to limit the results to. Null for all.
/// The .
- public static Query BuildQuery(byte[] vectorBytes, int top, VectorSearchOptions options, CollectionModel model, VectorPropertyModel vectorProperty, string[]? selectFields)
+ public static Query BuildQuery(byte[] vectorBytes, int top, VectorSearchOptions options, CollectionModel model, IVectorPropertyModel vectorProperty, string[]? selectFields)
{
// Build search query.
var redisLimit = top + options.Skip;
@@ -101,7 +101,7 @@ internal static Query BuildQuery(Expression> filter
///
/// The vector property to be used.
/// The distance function for the vector we want to search.
- public static string ResolveDistanceFunction(VectorPropertyModel vectorProperty)
+ public static string ResolveDistanceFunction(IVectorPropertyModel vectorProperty)
=> vectorProperty.DistanceFunction ?? DistanceFunction.CosineSimilarity;
///
diff --git a/dotnet/src/VectorData/Redis/RedisJsonMapper.cs b/dotnet/src/VectorData/Redis/RedisJsonMapper.cs
index 7755866b1c16..16c2d863fdaa 100644
--- a/dotnet/src/VectorData/Redis/RedisJsonMapper.cs
+++ b/dotnet/src/VectorData/Redis/RedisJsonMapper.cs
@@ -114,7 +114,7 @@ public TConsumerDataModel MapFromStorageToDataModel((string Key, JsonNode Node)
JsonArray and [JsonObject arrayEntryJsonObject] => arrayEntryJsonObject,
JsonValue when model.DataProperties.Count + (includeVectors ? model.VectorProperties.Count : 0) == 1 => new JsonObject
{
- [model.DataProperties.Concat(model.VectorProperties).First().StorageName] = storageModel.Node
+ [model.DataProperties.Concat(model.VectorProperties).First().StorageName] = storageModel.Node
},
_ => throw new InvalidOperationException($"Invalid data format for document with key '{storageModel.Key}'")
};
diff --git a/dotnet/src/VectorData/SqlServer/SqlServerCollection.cs b/dotnet/src/VectorData/SqlServer/SqlServerCollection.cs
index 470be57ab494..bfc5ed3b22dd 100644
--- a/dotnet/src/VectorData/SqlServer/SqlServerCollection.cs
+++ b/dotnet/src/VectorData/SqlServer/SqlServerCollection.cs
@@ -352,7 +352,7 @@ public override async Task UpsertAsync(TRecord record, CancellationToken cancell
{
Verify.NotNull(record);
- Dictionary>? generatedEmbeddings = null;
+ Dictionary>? generatedEmbeddings = null;
var vectorPropertyCount = this._model.VectorProperties.Count;
for (var i = 0; i < vectorPropertyCount; i++)
@@ -369,7 +369,7 @@ public override async Task UpsertAsync(TRecord record, CancellationToken cancell
// TODO: Ideally we'd group together vector properties using the same generator (and with the same input and output properties),
// and generate embeddings for them in a single batch. That's some more complexity though.
- generatedEmbeddings ??= new Dictionary>(vectorPropertyCount);
+ generatedEmbeddings ??= new Dictionary>(vectorPropertyCount);
generatedEmbeddings[vectorProperty] = [await vectorProperty.GenerateEmbeddingAsync(vectorProperty.GetValueAsObject(record), cancellationToken).ConfigureAwait(false)];
}
@@ -414,7 +414,7 @@ public override async Task UpsertAsync(IEnumerable records, Cancellatio
IReadOnlyList? recordsList = null;
// If an embedding generator is defined, invoke it once per property for all records.
- Dictionary>? generatedEmbeddings = null;
+ Dictionary>? generatedEmbeddings = null;
var vectorPropertyCount = this._model.VectorProperties.Count;
for (var i = 0; i < vectorPropertyCount; i++)
@@ -445,7 +445,7 @@ public override async Task UpsertAsync(IEnumerable records, Cancellatio
// TODO: Ideally we'd group together vector properties using the same generator (and with the same input and output properties),
// and generate embeddings for them in a single batch. That's some more complexity though.
- generatedEmbeddings ??= new Dictionary>(vectorPropertyCount);
+ generatedEmbeddings ??= new Dictionary>(vectorPropertyCount);
generatedEmbeddings[vectorProperty] = await vectorProperty.GenerateEmbeddingsAsync(records.Select(r => vectorProperty.GetValueAsObject(r)), cancellationToken).ConfigureAwait(false);
}
diff --git a/dotnet/src/VectorData/SqlServer/SqlServerCommandBuilder.cs b/dotnet/src/VectorData/SqlServer/SqlServerCommandBuilder.cs
index eabfc794f34e..9a3bf47a6ef0 100644
--- a/dotnet/src/VectorData/SqlServer/SqlServerCommandBuilder.cs
+++ b/dotnet/src/VectorData/SqlServer/SqlServerCommandBuilder.cs
@@ -103,7 +103,7 @@ internal static List CreateTable(
}
// Create full-text catalog and index for properties marked as IsFullTextIndexed
- var fullTextProperties = new List();
+ var fullTextProperties = new List();
foreach (var dataProperty in model.DataProperties)
{
if (dataProperty.IsFullTextIndexed)
@@ -228,7 +228,7 @@ FROM INFORMATION_SCHEMA.TABLES
/// Checks if the key property uses SQL Server IDENTITY (for int/bigint) as opposed to DEFAULT (for GUID).
/// IDENTITY columns require SET IDENTITY_INSERT ON to insert explicit values.
///
- private static bool UsesIdentity(KeyPropertyModel keyProperty)
+ private static bool UsesIdentity(IKeyPropertyModel keyProperty)
{
if (!keyProperty.IsAutoGenerated)
{
@@ -249,7 +249,7 @@ internal static bool Upsert(
CollectionModel model,
IEnumerable records,
int firstRecordIndex,
- Dictionary>? generatedEmbeddings)
+ Dictionary>? generatedEmbeddings)
{
var keyProperty = model.KeyProperty;
StringBuilder sb = new(500);
@@ -282,14 +282,14 @@ internal static bool Upsert(
foreach (var property in model.Properties)
{
// Skip key in VALUES when auto-generating
- if (property is KeyPropertyModel && skipKeyInInsert)
+ if (property is IKeyPropertyModel && skipKeyInInsert)
{
continue;
}
sb.AppendParameterName(property, ref paramIndex, out var paramName).Append(',');
- var value = property is VectorPropertyModel vectorProperty && generatedEmbeddings?.TryGetValue(vectorProperty, out var ge) == true
+ var value = property is IVectorPropertyModel vectorProperty && generatedEmbeddings?.TryGetValue(vectorProperty, out var ge) == true
? ge[firstRecordIndex + rowIndex]
: property.GetValueAsObject(record);
@@ -314,7 +314,7 @@ internal static bool Upsert(
sb.Append("UPDATE SET ");
foreach (var property in model.Properties)
{
- if (property is not KeyPropertyModel) // don't update the key
+ if (property is not IKeyPropertyModel) // don't update the key
{
sb.Append("t.").AppendIdentifier(property.StorageName).Append(" = s.").AppendIdentifier(property.StorageName).Append(',');
}
@@ -356,7 +356,7 @@ internal static bool Upsert(
internal static SqlCommand DeleteSingle(
SqlConnection connection, string? schema, string tableName,
- KeyPropertyModel keyProperty, object key)
+ IKeyPropertyModel keyProperty, object key)
{
SqlCommand command = connection.CreateCommand();
@@ -374,7 +374,7 @@ internal static SqlCommand DeleteSingle(
internal static bool DeleteMany(
SqlCommand command, string? schema, string tableName,
- KeyPropertyModel keyProperty, IEnumerable keys)
+ IKeyPropertyModel keyProperty, IEnumerable keys)
{
StringBuilder sb = new(100);
sb.Append("DELETE FROM ");
@@ -444,7 +444,7 @@ internal static bool SelectMany(
internal static SqlCommand SelectVector(
SqlConnection connection, string? schema, string tableName,
- VectorPropertyModel vectorProperty,
+ IVectorPropertyModel vectorProperty,
CollectionModel model,
int top,
VectorSearchOptions options,
@@ -460,7 +460,7 @@ internal static SqlCommand SelectVector(
private static SqlCommand SelectVectorWithVectorDistance(
SqlConnection connection, string? schema, string tableName,
- VectorPropertyModel vectorProperty,
+ IVectorPropertyModel vectorProperty,
CollectionModel model,
int top,
VectorSearchOptions options,
@@ -527,7 +527,7 @@ private static SqlCommand SelectVectorWithVectorDistance(
///
private static SqlCommand SelectVectorWithVectorSearch(
SqlConnection connection, string? schema, string tableName,
- VectorPropertyModel vectorProperty,
+ IVectorPropertyModel vectorProperty,
CollectionModel model,
int top,
VectorSearchOptions options,
@@ -577,8 +577,8 @@ private static SqlCommand SelectVectorWithVectorSearch(
internal static SqlCommand SelectHybrid(
SqlConnection connection, string? schema, string tableName,
- VectorPropertyModel vectorProperty,
- DataPropertyModel textProperty,
+ IVectorPropertyModel vectorProperty,
+ IDataPropertyModel textProperty,
CollectionModel model,
int top,
HybridSearchOptions options,
@@ -718,7 +718,7 @@ internal static SqlCommand SelectHybrid(
sb.Append("SELECT ");
foreach (var property in model.Properties)
{
- if (!options.IncludeVectors && property is VectorPropertyModel)
+ if (!options.IncludeVectors && property is IVectorPropertyModel)
{
continue;
}
@@ -806,7 +806,7 @@ internal static SqlCommand SelectWhere(
return command;
}
- internal static StringBuilder AppendParameterName(this StringBuilder sb, PropertyModel property, ref int paramIndex, out string parameterName)
+ internal static StringBuilder AppendParameterName(this StringBuilder sb, IPropertyModel property, ref int paramIndex, out string parameterName)
{
// In SQL Server, parameter names cannot be just a number like "@1".
// Parameter names must start with an alphabetic character or an underscore
@@ -865,7 +865,7 @@ internal static StringBuilder AppendIdentifier(this StringBuilder sb, string ide
}
private static StringBuilder AppendIdentifiers(this StringBuilder sb,
- IEnumerable properties,
+ IEnumerable properties,
string? prefix = null,
bool includeVectors = true,
bool skipKey = false)
@@ -873,12 +873,12 @@ private static StringBuilder AppendIdentifiers(this StringBuilder sb,
bool any = false;
foreach (var property in properties)
{
- if (!includeVectors && property is VectorPropertyModel)
+ if (!includeVectors && property is IVectorPropertyModel)
{
continue;
}
- if (skipKey && property is KeyPropertyModel)
+ if (skipKey && property is IKeyPropertyModel)
{
continue;
}
@@ -900,7 +900,7 @@ private static StringBuilder AppendIdentifiers(this StringBuilder sb,
}
private static StringBuilder AppendKeyParameterList(this StringBuilder sb,
- IEnumerable keys, SqlCommand command, KeyPropertyModel keyProperty, out bool emptyKeys)
+ IEnumerable keys, SqlCommand command, IKeyPropertyModel keyProperty, out bool emptyKeys)
{
int keyIndex = 0;
foreach (TKey key in keys)
@@ -957,7 +957,7 @@ private static SqlCommand CreateCommand(this SqlConnection connection, StringBui
return command;
}
- private static void AddParameter(this SqlCommand command, PropertyModel? property, string name, object? value)
+ private static void AddParameter(this SqlCommand command, IPropertyModel? property, string name, object? value)
{
switch (value)
{
@@ -998,7 +998,7 @@ private static void AddParameter(this SqlCommand command, PropertyModel? propert
}
}
- private static string Map(PropertyModel property)
+ private static string Map(IPropertyModel property)
=> (Nullable.GetUnderlyingType(property.Type) ?? property.Type) switch
{
Type t when t == typeof(byte) => "TINYINT",
@@ -1006,8 +1006,8 @@ private static string Map(PropertyModel property)
Type t when t == typeof(int) => "INT",
Type t when t == typeof(long) => "BIGINT",
Type t when t == typeof(Guid) => "UNIQUEIDENTIFIER",
- Type t when t == typeof(string) && property is KeyPropertyModel => "NVARCHAR(4000)",
- Type t when t == typeof(string) && property is DataPropertyModel { IsIndexed: true } => "NVARCHAR(4000)",
+ Type t when t == typeof(string) && property is IKeyPropertyModel => "NVARCHAR(4000)",
+ Type t when t == typeof(string) && property is IDataPropertyModel { IsIndexed: true } => "NVARCHAR(4000)",
Type t when t == typeof(string) => "NVARCHAR(MAX)",
Type t when t == typeof(byte[]) => "VARBINARY(MAX)",
Type t when t == typeof(bool) => "BIT",
@@ -1043,6 +1043,6 @@ private static string Map(PropertyModel property)
/// Returns whether VECTOR_SEARCH() (approximate/indexed search) should be used for the given vector property,
/// as opposed to VECTOR_DISTANCE() (exact/brute-force search).
///
- private static bool UseVectorSearch(VectorPropertyModel vectorProperty)
+ private static bool UseVectorSearch(IVectorPropertyModel vectorProperty)
=> vectorProperty.IndexKind is not (null or "" or IndexKind.Flat);
}
diff --git a/dotnet/src/VectorData/SqlServer/SqlServerFilterTranslator.cs b/dotnet/src/VectorData/SqlServer/SqlServerFilterTranslator.cs
index 0ccacd33334c..7ff4fdebc3ad 100644
--- a/dotnet/src/VectorData/SqlServer/SqlServerFilterTranslator.cs
+++ b/dotnet/src/VectorData/SqlServer/SqlServerFilterTranslator.cs
@@ -65,7 +65,7 @@ protected override void TranslateConstant(object? value, bool isSearchCondition)
}
}
- protected override void GenerateColumn(PropertyModel property, bool isSearchCondition = false)
+ protected override void GenerateColumn(IPropertyModel property, bool isSearchCondition = false)
{
// StorageName is considered to be a safe input, we quote and escape it mostly to produce valid SQL.
if (this._tableAlias is not null)
@@ -124,7 +124,7 @@ protected override void TranslateContainsOverParameterizedArray(Expression sourc
this._sql.Append(')');
}
- protected override void TranslateAnyContainsOverArrayColumn(PropertyModel property, object? values)
+ protected override void TranslateAnyContainsOverArrayColumn(IPropertyModel property, object? values)
{
// Translate r.Strings.Any(s => array.Contains(s)) to:
// EXISTS(SELECT 1 FROM OPENJSON(column) WHERE value IN ('a', 'b', 'c'))
diff --git a/dotnet/src/VectorData/SqlServer/SqlServerMapper.cs b/dotnet/src/VectorData/SqlServer/SqlServerMapper.cs
index 684d07e251fb..515e4774d878 100644
--- a/dotnet/src/VectorData/SqlServer/SqlServerMapper.cs
+++ b/dotnet/src/VectorData/SqlServer/SqlServerMapper.cs
@@ -61,7 +61,7 @@ public TRecord MapFromStorageToDataModel(SqlDataReader reader, bool includeVecto
return record;
- static void PopulateValue(SqlDataReader reader, PropertyModel property, object record)
+ static void PopulateValue(SqlDataReader reader, IPropertyModel property, object record)
{
try
{
diff --git a/dotnet/src/VectorData/SqliteVec/SqliteCollection.cs b/dotnet/src/VectorData/SqliteVec/SqliteCollection.cs
index 03d7849c1ddf..432923460f8c 100644
--- a/dotnet/src/VectorData/SqliteVec/SqliteCollection.cs
+++ b/dotnet/src/VectorData/SqliteVec/SqliteCollection.cs
@@ -546,7 +546,7 @@ private async Task DoUpsertAsync(IEnumerable records, CancellationToken
records = recordsList;
// If an embedding generator is defined, invoke it once per property for all records.
- Dictionary>>? generatedEmbeddings = null;
+ Dictionary>>? generatedEmbeddings = null;
var vectorPropertyCount = this._model.VectorProperties.Count;
for (var i = 0; i < vectorPropertyCount; i++)
@@ -563,7 +563,7 @@ private async Task DoUpsertAsync(IEnumerable records, CancellationToken
// TODO: Ideally we'd group together vector properties using the same generator (and with the same input and output properties),
// and generate embeddings for them in a single batch. That's some more complexity though.
- generatedEmbeddings ??= new Dictionary>>(vectorPropertyCount);
+ generatedEmbeddings ??= new Dictionary>>(vectorPropertyCount);
generatedEmbeddings[vectorProperty] = (IReadOnlyList>)await vectorProperty.GenerateEmbeddingsAsync(records.Select(r => vectorProperty.GetValueAsObject(r)), cancellationToken).ConfigureAwait(false);
}
@@ -588,7 +588,7 @@ private async Task DoUpsertAsync(IEnumerable records, CancellationToken
{
// If the key property is auto-generated, we need to read the generated keys from the database and inject them into the records
// (except for GUIDs which are generated client-side and have already been injected).
- if (keyProperty is KeyPropertyModel { IsAutoGenerated: true } && keyProperty.Type != typeof(Guid))
+ if (keyProperty is IKeyPropertyModel { IsAutoGenerated: true } && keyProperty.Type != typeof(Guid))
{
int? keyOrdinal = null;
diff --git a/dotnet/src/VectorData/SqliteVec/SqliteCommandBuilder.cs b/dotnet/src/VectorData/SqliteVec/SqliteCommandBuilder.cs
index 22ea94bdea9b..60d060281c5f 100644
--- a/dotnet/src/VectorData/SqliteVec/SqliteCommandBuilder.cs
+++ b/dotnet/src/VectorData/SqliteVec/SqliteCommandBuilder.cs
@@ -116,7 +116,7 @@ public static DbCommand BuildInsertCommand(
string tableName,
CollectionModel model,
IReadOnlyList records,
- Dictionary>>? generatedEmbeddings,
+ Dictionary>>? generatedEmbeddings,
bool data,
bool replaceIfExists = false)
{
@@ -125,7 +125,7 @@ public static DbCommand BuildInsertCommand(
var recordIndex = 0;
- var properties = model.KeyProperties.Concat(data ? model.DataProperties : (IEnumerable)model.VectorProperties).ToList();
+ var properties = model.KeyProperties.Concat(data ? model.DataProperties : (IEnumerable)model.VectorProperties).ToList();
var keyProperty = model.KeyProperty;
var isKeyPossiblyDatabaseGenerated = keyProperty.IsAutoGenerated && (keyProperty.Type == typeof(int) || keyProperty.Type == typeof(long));
@@ -148,7 +148,7 @@ public static DbCommand BuildInsertCommand(
var propertyIndex = 0;
foreach (var property in properties)
{
- if (property is KeyPropertyModel && isRecordKeyDatabaseGenerated)
+ if (property is IKeyPropertyModel && isRecordKeyDatabaseGenerated)
{
continue;
}
@@ -172,7 +172,7 @@ public static DbCommand BuildInsertCommand(
switch (property)
{
- case KeyPropertyModel { IsAutoGenerated: true }:
+ case IKeyPropertyModel { IsAutoGenerated: true }:
{
switch (value)
{
@@ -206,7 +206,7 @@ public static DbCommand BuildInsertCommand(
break;
}
- case VectorPropertyModel vectorProperty:
+ case IVectorPropertyModel vectorProperty:
{
if (generatedEmbeddings?[vectorProperty] is IReadOnlyList ge)
{
@@ -397,13 +397,13 @@ internal static StringBuilder AppendIdentifier(this StringBuilder sb, string ide
#region private
- private static StringBuilder AppendColumnNames(this StringBuilder builder, bool includeVectors, IReadOnlyList properties,
+ private static StringBuilder AppendColumnNames(this StringBuilder builder, bool includeVectors, IReadOnlyList properties,
string? vectorTableName = null, string? dataTableName = null)
{
foreach (var property in properties)
{
string? tableName = dataTableName;
- if (property is VectorPropertyModel)
+ if (property is IVectorPropertyModel)
{
if (!includeVectors)
{
diff --git a/dotnet/src/VectorData/SqliteVec/SqliteFilterTranslator.cs b/dotnet/src/VectorData/SqliteVec/SqliteFilterTranslator.cs
index 3baed486eead..046d11dcde89 100644
--- a/dotnet/src/VectorData/SqliteVec/SqliteFilterTranslator.cs
+++ b/dotnet/src/VectorData/SqliteVec/SqliteFilterTranslator.cs
@@ -52,7 +52,7 @@ protected override void TranslateContainsOverArrayColumn(Expression source, Expr
=> throw new NotSupportedException("Unsupported Contains expression");
// TODO: support Any over array fields (#10343)
- protected override void TranslateAnyContainsOverArrayColumn(PropertyModel property, object? values)
+ protected override void TranslateAnyContainsOverArrayColumn(IPropertyModel property, object? values)
=> throw new NotSupportedException("Unsupported method call: Enumerable.Any");
protected override void TranslateContainsOverParameterizedArray(Expression source, Expression item, object? value)
diff --git a/dotnet/src/VectorData/SqliteVec/SqlitePropertyMapping.cs b/dotnet/src/VectorData/SqliteVec/SqlitePropertyMapping.cs
index 4c4ac08859ee..bde38e812eae 100644
--- a/dotnet/src/VectorData/SqliteVec/SqlitePropertyMapping.cs
+++ b/dotnet/src/VectorData/SqliteVec/SqlitePropertyMapping.cs
@@ -24,7 +24,7 @@ public static byte[] MapVectorForStorageModel(ReadOnlyMemory memory)
return byteArray;
}
- public static List GetColumns(IReadOnlyList properties, bool data)
+ public static List GetColumns(IReadOnlyList properties, bool data)
{
const string DistanceMetricConfigurationName = "distance_metric";
@@ -37,7 +37,7 @@ public static List GetColumns(IReadOnlyList propert
string propertyType;
Dictionary? configuration = null;
- if (property is VectorPropertyModel vectorProperty)
+ if (property is IVectorPropertyModel vectorProperty)
{
if (data)
{
@@ -50,7 +50,7 @@ public static List GetColumns(IReadOnlyList propert
[DistanceMetricConfigurationName] = GetDistanceMetric(vectorProperty)
};
}
- else if (property is DataPropertyModel dataProperty)
+ else if (property is IDataPropertyModel dataProperty)
{
if (!data)
{
@@ -62,7 +62,7 @@ public static List GetColumns(IReadOnlyList propert
else
{
// The Key column in included in both Vector and Data tables.
- Debug.Assert(property is KeyPropertyModel, "property is VectorStoreRecordKeyPropertyModel");
+ Debug.Assert(property is IKeyPropertyModel, "property is not an IKeyPropertyModel");
propertyType = GetStorageDataPropertyType(property);
isPrimary = true;
@@ -72,7 +72,7 @@ public static List GetColumns(IReadOnlyList propert
{
IsNullable = property.IsNullable,
Configuration = configuration,
- HasIndex = property is DataPropertyModel { IsIndexed: true }
+ HasIndex = property is IDataPropertyModel { IsIndexed: true }
};
columns.Add(column);
@@ -93,7 +93,7 @@ public static List GetColumns(IReadOnlyList propert
#region private
- private static string GetStorageDataPropertyType(PropertyModel property)
+ private static string GetStorageDataPropertyType(IPropertyModel property)
=> property.Type switch
{
// Integer types
@@ -129,7 +129,7 @@ private static string GetStorageDataPropertyType(PropertyModel property)
_ => throw new NotSupportedException($"Property '{property.ModelName}' has type '{property.Type.Name}', which is not supported by SQLite connector.")
};
- private static string GetDistanceMetric(VectorPropertyModel vectorProperty)
+ private static string GetDistanceMetric(IVectorPropertyModel vectorProperty)
=> vectorProperty.DistanceFunction switch
{
DistanceFunction.CosineDistance or null => "cosine",
@@ -138,7 +138,7 @@ private static string GetDistanceMetric(VectorPropertyModel vectorProperty)
_ => throw new NotSupportedException($"Distance function '{vectorProperty.DistanceFunction}' for {nameof(VectorStoreVectorProperty)} '{vectorProperty.ModelName}' is not supported by the SQLite connector.")
};
- private static string GetStorageVectorPropertyType(VectorPropertyModel vectorProperty)
+ private static string GetStorageVectorPropertyType(IVectorPropertyModel vectorProperty)
=> $"FLOAT[{vectorProperty.Dimensions}]";
#endregion
diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionJsonModelBuilder.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionJsonModelBuilder.cs
index 2b7584b8dd0c..90a2db9433f3 100644
--- a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionJsonModelBuilder.cs
+++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionJsonModelBuilder.cs
@@ -89,13 +89,11 @@ protected override void Customize()
if (keyPropertyWithReservedName)
{
- // Somewhat hacky:
// Some providers (Weaviate, Cosmos NoSQL) have a fixed, reserved storage name for keys (id), and at the same time use an external
// JSON serializer to serialize the entire user POCO. Since the serializer is unaware of the reserved storage name, it will produce
// a storage name as usual, based on the .NET property's name, possibly with a naming policy applied to it. The connector then needs
// to look that up and replace with the reserved name.
- // So we store the policy-transformed name, as StorageName contains the reserved name.
- property.TemporaryStorageName = storageName;
+ ((KeyPropertyModel)property).SerializedKeyName = storageName;
}
else
{
diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionModel.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionModel.cs
index 1d67a7aa2026..06a130704a44 100644
--- a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionModel.cs
+++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionModel.cs
@@ -19,36 +19,36 @@ namespace Microsoft.Extensions.VectorData.ProviderServices;
public sealed class CollectionModel
{
private readonly Type _recordType;
- private readonly IRecordCreator _recordCreator;
+ private readonly Func _recordFactory;
- private KeyPropertyModel? _singleKeyProperty;
- private VectorPropertyModel? _singleVectorProperty;
- private DataPropertyModel? _singleFullTextSearchProperty;
+ private IKeyPropertyModel? _singleKeyProperty;
+ private IVectorPropertyModel? _singleVectorProperty;
+ private IDataPropertyModel? _singleFullTextSearchProperty;
///
/// Gets the key properties of the record.
///
- public IReadOnlyList KeyProperties { get; }
+ public IReadOnlyList KeyProperties { get; }
///
/// Gets the data properties of the record.
///
- public IReadOnlyList DataProperties { get; }
+ public IReadOnlyList DataProperties { get; }
///
/// Gets the vector properties of the record.
///
- public IReadOnlyList VectorProperties { get; }
+ public IReadOnlyList VectorProperties { get; }
///
/// Gets all properties of the record, of all types.
///
- public IReadOnlyList Properties { get; }
+ public IReadOnlyList Properties { get; }
///
/// Gets all properties of the record, of all types, indexed by their model name.
///
- public IReadOnlyDictionary PropertyMap { get; }
+ public IReadOnlyDictionary PropertyMap { get; }
///
/// Gets a value that indicates whether any of the vector properties in the model require embedding generation.
@@ -57,20 +57,20 @@ public sealed class CollectionModel
internal CollectionModel(
Type recordType,
- IRecordCreator recordCreator,
+ Func recordFactory,
IReadOnlyList keyProperties,
IReadOnlyList dataProperties,
IReadOnlyList vectorProperties,
IReadOnlyDictionary propertyMap)
{
this._recordType = recordType;
- this._recordCreator = recordCreator;
+ this._recordFactory = recordFactory;
this.KeyProperties = keyProperties;
this.DataProperties = dataProperties;
this.VectorProperties = vectorProperties;
- this.PropertyMap = propertyMap;
- this.Properties = propertyMap.Values.ToList();
+ this.PropertyMap = propertyMap.ToDictionary(kvp => kvp.Key, kvp => (IPropertyModel)kvp.Value);
+ this.Properties = this.PropertyMap.Values.ToList();
this.EmbeddingGenerationRequired = vectorProperties.Any(p => p.EmbeddingType != p.Type);
}
@@ -78,13 +78,13 @@ internal CollectionModel(
///
/// Returns the single key property in the model, and throws if there are multiple key properties.
///
- public KeyPropertyModel KeyProperty => this._singleKeyProperty ??= this.KeyProperties.Single();
+ public IKeyPropertyModel KeyProperty => this._singleKeyProperty ??= this.KeyProperties.Single();
///
/// Returns the single vector property in the model, and throws if there are multiple vector properties.
/// Suitable for connectors where validation is in place for single vectors only ( ).
///
- public VectorPropertyModel VectorProperty => this._singleVectorProperty ??= this.VectorProperties.Single();
+ public IVectorPropertyModel VectorProperty => this._singleVectorProperty ??= this.VectorProperties.Single();
///
/// Instantiates a new record of the specified type.
@@ -97,7 +97,7 @@ public TRecord CreateRecord()
{
Debug.Assert(typeof(TRecord) == this._recordType, "Type mismatch between record type and model type.");
- return this._recordCreator.Create();
+ return (TRecord)this._recordFactory();
}
///
@@ -106,11 +106,11 @@ public TRecord CreateRecord()
///
/// The search options, which defines the vector property name.
/// The provided property name is not a valid text data property name. OR No name was provided and there's more than one vector property.
- public VectorPropertyModel GetVectorPropertyOrSingle(VectorSearchOptions searchOptions)
+ public IVectorPropertyModel GetVectorPropertyOrSingle(VectorSearchOptions searchOptions)
{
if (searchOptions.VectorProperty is not null)
{
- return this.GetMatchingProperty(searchOptions.VectorProperty, data: false);
+ return this.GetMatchingProperty(searchOptions.VectorProperty, data: false);
}
// If vector property name is not provided, check if there is a single vector property, or throw if there are no vectors or more than one.
@@ -140,11 +140,11 @@ public VectorPropertyModel GetVectorPropertyOrSingle(VectorSearchOption
///
/// The full text search property selector.
/// The provided property name is not a valid text data property name. OR No name was provided and there's more than one text data property with full text search indexing enabled.
- public DataPropertyModel GetFullTextDataPropertyOrSingle(Expression>? expression)
+ public IDataPropertyModel GetFullTextDataPropertyOrSingle(Expression>? expression)
{
if (expression is not null)
{
- var property = this.GetMatchingProperty(expression, data: true);
+ var property = this.GetMatchingProperty(expression, data: true);
return property.IsFullTextIndexed
? property
@@ -184,11 +184,11 @@ public DataPropertyModel GetFullTextDataPropertyOrSingle(Expression
/// The property selector.
/// The provided property name is not a valid data or key property name.
- public PropertyModel GetDataOrKeyProperty(Expression> expression)
- => this.GetMatchingProperty(expression, data: true);
+ public IPropertyModel GetDataOrKeyProperty(Expression> expression)
+ => this.GetMatchingProperty(expression, data: true);
private TProperty GetMatchingProperty(Expression> expression, bool data)
- where TProperty : PropertyModel
+ where TProperty : IPropertyModel
{
var node = expression.Body;
diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionModelBuilder.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionModelBuilder.cs
index 4932b81cdcb9..3b6f6ccc6b51 100644
--- a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionModelBuilder.cs
+++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionModelBuilder.cs
@@ -85,26 +85,43 @@ public virtual CollectionModel Build(Type recordType, Type keyType, VectorStoreC
this.DefaultEmbeddingGenerator = definition?.EmbeddingGenerator ?? defaultEmbeddingGenerator;
- this.ProcessTypeProperties(recordType, definition);
-
+ // Build a lookup of definition properties by name for matching with CLR properties.
+ Dictionary? definitionByName = null;
if (definition is not null)
{
- this.ProcessRecordDefinition(definition, recordType);
+ definitionByName = [];
+ foreach (var p in definition.Properties)
+ {
+ definitionByName[p.Name] = p;
+ }
}
- // Go over the properties, set the PropertyInfos to point to the .NET type's properties and validate type compatibility.
+ // Process CLR properties, matching to definition properties where available.
+ // TODO: This traverses the CLR type's properties, making it incompatible with trimming (and NativeAOT).
+ // TODO: We could put [DynamicallyAccessedMembers] to preserve all properties, but that approach wouldn't
+ // TODO: work with hierarchical data models (#10957).
+ foreach (var clrProperty in recordType.GetProperties())
+ {
+ VectorStoreProperty? definitionProperty = null;
+ _ = definitionByName?.TryGetValue(clrProperty.Name, out definitionProperty);
+
+ this.ProcessProperty(clrProperty, definitionProperty, recordType);
+ }
+
+ // Go over the properties, configure POCO accessors 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 = recordType.GetProperty(property.ModelName)
+ var clrProperty = recordType.GetProperty(property.ModelName)
?? throw new InvalidOperationException($"Property '{property.ModelName}' not found on CLR type '{recordType.FullName}'.");
- var clrPropertyType = property.PropertyInfo.PropertyType;
+ var clrPropertyType = clrProperty.PropertyType;
if ((Nullable.GetUnderlyingType(clrPropertyType) ?? clrPropertyType) != (Nullable.GetUnderlyingType(property.Type) ?? property.Type))
{
throw new InvalidOperationException(
- $"Property '{property.ModelName}' has a different CLR type in the record definition ('{property.Type.Name}') and on the .NET property ('{property.PropertyInfo.PropertyType}').");
+ $"Property '{property.ModelName}' has a different CLR type in the record definition ('{property.Type.Name}') and on the .NET property ('{clrProperty.PropertyType}').");
}
+
+ property.ConfigurePocoAccessors(clrProperty);
}
this.Customize();
@@ -116,7 +133,7 @@ public virtual CollectionModel Build(Type recordType, Type keyType, VectorStoreC
throw new NotSupportedException($"Type '{recordType.Name}' must have a parameterless constructor.");
}
- return new(recordType, new ActivatorBasedRecordCreator(), this.KeyProperties, this.DataProperties, this.VectorProperties, this.PropertyMap);
+ return new(recordType, () => Activator.CreateInstance(recordType)!, this.KeyProperties, this.DataProperties, this.VectorProperties, this.PropertyMap);
}
///
@@ -132,274 +149,186 @@ public virtual CollectionModel BuildDynamic(VectorStoreCollectionDefinition defi
}
this.DefaultEmbeddingGenerator = defaultEmbeddingGenerator;
- this.ProcessRecordDefinition(definition, type: null);
+
+ foreach (var defProp in definition.Properties)
+ {
+ this.ProcessProperty(clrProperty: null, defProp, type: null);
+ }
+
this.Customize();
this.Validate(type: null, definition);
- return new(typeof(Dictionary), new DynamicRecordCreator(), this.KeyProperties, this.DataProperties, this.VectorProperties, this.PropertyMap);
+ foreach (var property in this.Properties)
+ {
+ property.ConfigureDynamicAccessors();
+ }
+
+ return new(typeof(Dictionary), static () => new Dictionary(), this.KeyProperties, this.DataProperties, this.VectorProperties, this.PropertyMap);
}
///
- /// As part of building the model, this method processes the properties of the given ,
- /// detecting and reading attributes that affect the model. Not called for dynamic mapping scenarios.
+ /// As part of building the model, this method processes a single property, accepting both a CLR
+ /// (from which attributes are read) and a from the user-provided record definition.
+ /// Either may be , but not both.
+ /// When both are provided, the record definition values override attribute-configured values.
///
- // TODO: This traverses the CLR type's properties, making it incompatible with trimming (and NativeAOT).
- // TODO: We could put [DynamicallyAccessedMembers] to preserve all properties, but that approach wouldn't
- // TODO: work with hierarchical data models (#10957).
- [RequiresUnreferencedCode("Traverses the CLR type's properties with reflection, so not compatible with trimming")]
- protected virtual void ProcessTypeProperties(Type type, VectorStoreCollectionDefinition? definition)
+ protected virtual void ProcessProperty(PropertyInfo? clrProperty, VectorStoreProperty? definitionProperty, Type? type)
{
- // We want to allow the user-provided record definition to override anything configured via attributes
- // (allowing the same CLR type + attributes to be used with different record definitions).
- foreach (var clrProperty in type.GetProperties())
+ Debug.Assert(clrProperty is not null || definitionProperty is not null);
+
+ VectorStoreKeyAttribute? keyAttribute = null;
+ VectorStoreDataAttribute? dataAttribute = null;
+ VectorStoreVectorAttribute? vectorAttribute = null;
+
+ if (clrProperty is not null)
{
- PropertyModel? property = null;
- string? storageName = null;
+ // Read attributes from CLR property.
+ keyAttribute = clrProperty.GetCustomAttribute();
+ dataAttribute = clrProperty.GetCustomAttribute();
+ vectorAttribute = clrProperty.GetCustomAttribute();
- if (clrProperty.GetCustomAttribute() is { } keyAttribute)
+ // Validate that at most one mapping attribute is present.
+ if ((keyAttribute is not null ? 1 : 0) + (dataAttribute is not null ? 1 : 0) + (vectorAttribute is not null ? 1 : 0) > 1)
{
- var keyProperty = new KeyPropertyModel(clrProperty.Name, clrProperty.PropertyType);
- keyProperty.IsAutoGenerated = keyAttribute.IsAutoGeneratedNullable ?? this.SupportsKeyAutoGeneration(keyProperty.Type);
- this.KeyProperties.Add(keyProperty);
- storageName = keyAttribute.StorageName;
- property = keyProperty;
+ throw new InvalidOperationException(
+ $"Property '{type!.Name}.{clrProperty.Name}' has multiple of {nameof(VectorStoreKeyAttribute)}, {nameof(VectorStoreDataAttribute)} or {nameof(VectorStoreVectorAttribute)}. Only one of these attributes can be specified on a property.");
}
- if (clrProperty.GetCustomAttribute() is { } dataAttribute)
+ // If no mapping attribute and no definition, skip this property.
+ if (keyAttribute is null && dataAttribute is null && vectorAttribute is null && definitionProperty is null)
{
- if (property is not null)
- {
- // TODO: Test
- throw new InvalidOperationException($"Property '{type.Name}.{clrProperty.Name}' has multiple of {nameof(VectorStoreKeyAttribute)}, {nameof(VectorStoreDataAttribute)} or {nameof(VectorStoreVectorAttribute)}. Only one of these attributes can be specified on a property.");
- }
-
- var dataProperty = new DataPropertyModel(clrProperty.Name, clrProperty.PropertyType)
- {
- IsIndexed = dataAttribute.IsIndexed,
- IsFullTextIndexed = dataAttribute.IsFullTextIndexed,
- };
-
- this.DataProperties.Add(dataProperty);
- storageName = dataAttribute.StorageName;
- property = dataProperty;
+ return;
}
- if (clrProperty.GetCustomAttribute() is { } vectorAttribute)
+ // Validate kind compatibility between attribute and definition.
+ if (definitionProperty is not null
+ && ((keyAttribute is not null && definitionProperty is not VectorStoreKeyProperty)
+ || (dataAttribute is not null && definitionProperty is not VectorStoreDataProperty)
+ || (vectorAttribute is not null && definitionProperty is not VectorStoreVectorProperty)))
{
- if (property is not null)
+ string definitionKind = definitionProperty switch
{
- throw new InvalidOperationException($"Only one of {nameof(VectorStoreKeyAttribute)}, {nameof(VectorStoreDataAttribute)} and {nameof(VectorStoreVectorAttribute)} can be applied to a property.");
- }
-
- // If a record definition exists for the property, we must instantiate it via that definition, as the user may be using
- // a generic VectorStoreRecordVectorProperty for a custom input type.
- var vectorProperty = definition?.Properties.FirstOrDefault(p => p.Name == clrProperty.Name) is VectorStoreVectorProperty definitionVectorProperty
- ? definitionVectorProperty.CreatePropertyModel()
- : new VectorPropertyModel(clrProperty.Name, clrProperty.PropertyType);
-
- vectorProperty.Dimensions = vectorAttribute.Dimensions;
- vectorProperty.IndexKind = vectorAttribute.IndexKind;
- vectorProperty.DistanceFunction = vectorAttribute.DistanceFunction;
+ VectorStoreKeyProperty => "key",
+ VectorStoreDataProperty => "data",
+ VectorStoreVectorProperty => "vector",
+ _ => throw new ArgumentException($"Unknown type '{definitionProperty.GetType().FullName}' in vector store record definition.")
+ };
- // Set up the embedding generator for the property. For this pass over .NET properties, we only have the default embedding generator (configured)
- // at the collection/store level) - this may get overridden later by the record definition.
+ throw new InvalidOperationException(
+ $"Property '{clrProperty.Name}' is present in the {nameof(VectorStoreCollectionDefinition)} as a {definitionKind} property, but the .NET property on type '{type?.Name}' has an incompatible attribute.");
+ }
+ }
- // 1. We also attempt to set the EmbeddingType for the property. If the type is natively supported (e.g. ReadOnlyMemory), we use that.
- // 2. If an embedding generator is configured, we try to resolve the embedding type from that. This allows users to just e.g. stick an
- // IEmbeddingGenerator in DI, define a string property as their vector property, and as long as the embedding generator is compatible (supports
- // string and ROM, assuming that's what the connector requires), everything just works.
- // Note that inferring the embedding type from the IEmbeddingGenerator isn't trivial, involving both connector logic (around which embedding
- // types are supported/preferred), as well as the vector property type (which knows about supported input types).
- // 3. Otherwise, if we can't infer the embedding type from the generator (no generator or the default generator isn't compatible), we leave it
- // null to allow it to get configured later (e.g. via a property-specific generator configured in the record definition).
+ string propertyName = clrProperty?.Name ?? definitionProperty!.Name;
+ Type propertyType = clrProperty?.PropertyType
+ ?? definitionProperty!.Type
+ ?? throw new InvalidOperationException(VectorDataStrings.MissingTypeOnPropertyDefinition(definitionProperty!));
- vectorProperty.EmbeddingGenerator = this.DefaultEmbeddingGenerator;
+ PropertyModel property;
+ string? attributeStorageName = null;
- if (this.IsVectorPropertyTypeValid(clrProperty.PropertyType, out _))
- {
- vectorProperty.EmbeddingType = clrProperty.PropertyType;
-
- // Even for native types, if an embedding generator is configured, resolve the handler
- // so that search can convert arbitrary inputs (e.g. string) to embeddings.
- // Since the property type is a native vector type (not the input type for the generator),
- // we use CanGenerateEmbedding which checks if the generator can produce the embedding output type
- // regardless of input type.
- if (this.DefaultEmbeddingGenerator is not null)
- {
- vectorProperty.EmbeddingGenerationDispatcher = this.ResolveSearchOnlyEmbeddingHandler(vectorProperty, this.DefaultEmbeddingGenerator);
- }
- }
- else if (this.DefaultEmbeddingGenerator is not null)
- {
- // The property type isn't a valid embedding type (e.g. ReadOnlyMemory), but an embedding generator is configured.
- // Try to resolve the embedding type from that: if the configured generator supports translating the input type (e.g. string) to
- // an output type supported by the provider, we set that as the embedding type.
- // Note that this can fail (if the configured generator doesn't support the required translation). In that case, EmbeddingType
- // remains null, and we may succeed configuring it later (e.g. from the record definition). If that fails, we throw in validation at the end.
- var (embeddingType, handler) = this.ResolveEmbeddingType(vectorProperty, this.DefaultEmbeddingGenerator, userRequestedEmbeddingType: null);
- vectorProperty.EmbeddingType = embeddingType;
- vectorProperty.EmbeddingGenerationDispatcher = handler;
- }
- else
- {
- // If the property type isn't valid and there's no embedding generator, that's an error.
- // However, we throw only later in validation, to allow e.g. for arbitrary provider customization after this step.
- }
+ if (keyAttribute is not null || definitionProperty is VectorStoreKeyProperty)
+ {
+ var keyProperty = new KeyPropertyModel(propertyName, propertyType);
- this.VectorProperties.Add(vectorProperty);
- storageName = vectorAttribute.StorageName;
- property = vectorProperty;
+ if (keyAttribute is not null)
+ {
+ keyProperty.IsAutoGenerated = keyAttribute.IsAutoGeneratedNullable ?? this.SupportsKeyAutoGeneration(keyProperty.Type);
+ attributeStorageName = keyAttribute.StorageName;
}
- if (property is null)
+ // Definition values override attribute values.
+ if (definitionProperty is VectorStoreKeyProperty defKey)
{
- // No mapping attribute was found, ignore this property.
- continue;
+ keyProperty.IsAutoGenerated = defKey.IsAutoGenerated ?? this.SupportsKeyAutoGeneration(keyProperty.Type);
}
- this.SetPropertyStorageName(property, storageName, type);
-
- property.PropertyInfo = clrProperty;
- this.PropertyMap.Add(clrProperty.Name, property);
+ this.KeyProperties.Add(keyProperty);
+ property = keyProperty;
}
- }
-
- ///
- /// Processes the given as part of building the model.
- ///
- protected virtual void ProcessRecordDefinition(VectorStoreCollectionDefinition definition, Type? type)
- {
- foreach (VectorStoreProperty definitionProperty in definition.Properties)
+ else if (dataAttribute is not null || definitionProperty is VectorStoreDataProperty)
{
- if (!this.PropertyMap.TryGetValue(definitionProperty.Name, out var property))
+ var dataProperty = new DataPropertyModel(propertyName, propertyType);
+
+ if (dataAttribute is not null)
{
- // Property wasn't found attribute-annotated on the CLR type, so we need to add it.
+ dataProperty.IsIndexed = dataAttribute.IsIndexed;
+ dataProperty.IsFullTextIndexed = dataAttribute.IsFullTextIndexed;
+ attributeStorageName = dataAttribute.StorageName;
+ }
- var propertyType = definitionProperty.Type
- ?? throw new InvalidOperationException(VectorDataStrings.MissingTypeOnPropertyDefinition(definitionProperty));
- switch (definitionProperty)
- {
- case VectorStoreKeyProperty definitionKeyProperty:
- var keyProperty = new KeyPropertyModel(definitionKeyProperty.Name, propertyType);
- this.KeyProperties.Add(keyProperty);
- this.PropertyMap.Add(definitionKeyProperty.Name, keyProperty);
- property = keyProperty;
- break;
- case VectorStoreDataProperty definitionDataProperty:
- var dataProperty = new DataPropertyModel(definitionDataProperty.Name, propertyType);
- this.DataProperties.Add(dataProperty);
- this.PropertyMap.Add(definitionDataProperty.Name, dataProperty);
- property = dataProperty;
- break;
- case VectorStoreVectorProperty definitionVectorProperty:
- var vectorProperty = definitionVectorProperty.CreatePropertyModel();
- this.VectorProperties.Add(vectorProperty);
- this.PropertyMap.Add(definitionVectorProperty.Name, vectorProperty);
- property = vectorProperty;
- break;
- default:
- throw new ArgumentException($"Unknown type '{definitionProperty.GetType().FullName}' in vector store record definition.");
- }
+ // Definition values override attribute values.
+ if (definitionProperty is VectorStoreDataProperty defData)
+ {
+ dataProperty.IsIndexed = defData.IsIndexed;
+ dataProperty.IsFullTextIndexed = defData.IsFullTextIndexed;
}
- this.SetPropertyStorageName(property, definitionProperty.StorageName, type);
+ this.DataProperties.Add(dataProperty);
+ property = dataProperty;
+ }
+ else if (vectorAttribute is not null || definitionProperty is VectorStoreVectorProperty)
+ {
+ // If a definition exists, create via the definition to preserve generic type info (VectorStoreVectorProperty).
+ var vectorProperty = definitionProperty is VectorStoreVectorProperty defVec
+ ? defVec.CreatePropertyModel()
+ : new VectorPropertyModel(propertyName, propertyType);
- // Copy provider-specific properties if present
- if (definitionProperty.ProviderAnnotations is not null)
+ if (vectorAttribute is not null)
{
- property.ProviderAnnotations = new Dictionary(definitionProperty.ProviderAnnotations);
+ vectorProperty.Dimensions = vectorAttribute.Dimensions;
+ vectorProperty.IndexKind = vectorAttribute.IndexKind;
+ vectorProperty.DistanceFunction = vectorAttribute.DistanceFunction;
+ attributeStorageName = vectorAttribute.StorageName;
}
- switch (definitionProperty)
+ // Definition values override attribute values.
+ if (definitionProperty is VectorStoreVectorProperty defVectorProp)
{
- case VectorStoreKeyProperty definitionKeyProperty:
- if (property is not KeyPropertyModel keyPropertyModel)
- {
- throw new InvalidOperationException(
- $"Property '{property.ModelName}' is present in the {nameof(VectorStoreCollectionDefinition)} as a key property, but the .NET property on type '{type?.Name}' has an incompatible attribute.");
- }
-
- keyPropertyModel.IsAutoGenerated = definitionKeyProperty.IsAutoGenerated ?? this.SupportsKeyAutoGeneration(keyPropertyModel.Type);
-
- break;
+ vectorProperty.Dimensions = defVectorProp.Dimensions;
- case VectorStoreDataProperty definitionDataProperty:
- if (property is not DataPropertyModel dataProperty)
- {
- throw new InvalidOperationException(
- $"Property '{property.ModelName}' is present in the {nameof(VectorStoreCollectionDefinition)} as a data property, but the .NET property on type '{type?.Name}' has an incompatible attribute.");
- }
-
- dataProperty.IsIndexed = definitionDataProperty.IsIndexed;
- dataProperty.IsFullTextIndexed = definitionDataProperty.IsFullTextIndexed;
-
- break;
-
- case VectorStoreVectorProperty definitionVectorProperty:
- if (property is not VectorPropertyModel vectorProperty)
- {
- throw new InvalidOperationException(
- $"Property '{property.ModelName}' is present in the {nameof(VectorStoreCollectionDefinition)} as a vector property, but the .NET property on type '{type?.Name}' has an incompatible attribute.");
- }
-
- vectorProperty.Dimensions = definitionVectorProperty.Dimensions;
-
- if (definitionVectorProperty.IndexKind is not null)
- {
- vectorProperty.IndexKind = definitionVectorProperty.IndexKind;
- }
+ if (defVectorProp.IndexKind is not null)
+ {
+ vectorProperty.IndexKind = defVectorProp.IndexKind;
+ }
- if (definitionVectorProperty.DistanceFunction is not null)
- {
- vectorProperty.DistanceFunction = definitionVectorProperty.DistanceFunction;
- }
+ if (defVectorProp.DistanceFunction is not null)
+ {
+ vectorProperty.DistanceFunction = defVectorProp.DistanceFunction;
+ }
+ }
- // See comment above in ProcessTypeProperties() on embedding generation.
+ this.ConfigureVectorPropertyEmbedding(
+ vectorProperty,
+ (definitionProperty as VectorStoreVectorProperty)?.EmbeddingGenerator ?? this.DefaultEmbeddingGenerator,
+ (definitionProperty as VectorStoreVectorProperty)?.EmbeddingType);
- vectorProperty.EmbeddingGenerator = definitionVectorProperty.EmbeddingGenerator ?? this.DefaultEmbeddingGenerator;
+ this.VectorProperties.Add(vectorProperty);
+ property = vectorProperty;
+ }
+ else
+ {
+ throw new UnreachableException();
+ }
- if (this.IsVectorPropertyTypeValid(vectorProperty.Type, out _))
- {
- if (definitionVectorProperty.EmbeddingType is not null && definitionVectorProperty.EmbeddingType != vectorProperty.Type)
- {
- throw new InvalidOperationException(VectorDataStrings.DifferentEmbeddingTypeSpecifiedForNativelySupportedType(vectorProperty, definitionVectorProperty.EmbeddingType));
- }
-
- vectorProperty.EmbeddingType = definitionVectorProperty.Type;
-
- // Even for native types, if an embedding generator is configured, resolve the handler
- // so that search can convert arbitrary inputs (e.g. string) to embeddings.
- // Since the property type is a native vector type (not the input type for the generator),
- // we use CanGenerateEmbedding which checks if the generator can produce the embedding output type
- // regardless of input type.
- if (vectorProperty.EmbeddingGenerator is not null)
- {
- vectorProperty.EmbeddingGenerationDispatcher = this.ResolveSearchOnlyEmbeddingHandler(vectorProperty, vectorProperty.EmbeddingGenerator);
- }
- }
- else if (vectorProperty.EmbeddingGenerator is not null)
- {
- // The property type isn't a valid embedding type (e.g. ReadOnlyMemory), but an embedding generator is configured.
- // Try to resolve the embedding type from the generator: if the configured generator supports translating the input type (e.g. string) to
- // an output type supported by the provider, we set that as the embedding type.
- // Note that this can fail (if the configured generator doesn't support the required translation). In that case, EmbeddingType
- // remains null - we defer throwing to the validation phase at the end, to allow for possible later provider customization later.
- var (embeddingType, handler) = this.ResolveEmbeddingType(vectorProperty, vectorProperty.EmbeddingGenerator, definitionVectorProperty.EmbeddingType);
- vectorProperty.EmbeddingType = embeddingType;
- vectorProperty.EmbeddingGenerationDispatcher = handler;
- }
- else
- {
- // If the property type isn't valid and there's no embedding generator, that's an error.
- // However, we throw only later in validation, to allow e.g. for arbitrary provider customization after this step.
- }
+ // Apply storage name: attribute first, then definition (which takes precedence).
+ this.SetPropertyStorageName(property, attributeStorageName, type);
+ if (definitionProperty is not null)
+ {
+ this.SetPropertyStorageName(property, definitionProperty.StorageName, type);
+ }
- break;
+ if (definitionProperty?.ProviderAnnotations is not null)
+ {
+ property.ProviderAnnotations = new Dictionary(definitionProperty.ProviderAnnotations);
+ }
- default:
- throw new ArgumentException($"Unknown type '{definitionProperty.GetType().FullName}' in vector store record definition.");
- }
+ if (clrProperty is not null)
+ {
+ property.PropertyInfo = clrProperty;
}
+
+ this.PropertyMap.Add(propertyName, property);
}
private void SetPropertyStorageName(PropertyModel property, string? storageName, Type? type)
@@ -475,6 +404,55 @@ private void SetPropertyStorageName(PropertyModel property, string? storageName,
return null;
}
+ ///
+ /// Configures embedding generation for a vector property. Sets the embedding generator, resolves the embedding type,
+ /// and assigns the appropriate .
+ ///
+ ///
+ /// If the property's type is natively supported (e.g. of ), the embedding type
+ /// is set to the property's type; if a generator is also configured, a search-only dispatcher is resolved so that search can convert
+ /// arbitrary inputs (e.g. string) to embeddings.
+ /// Otherwise, if a generator is configured, the embedding type is resolved from it. If resolution fails, the embedding type remains
+ /// and an error is deferred to the validation phase.
+ ///
+ private void ConfigureVectorPropertyEmbedding(
+ VectorPropertyModel vectorProperty,
+ IEmbeddingGenerator? embeddingGenerator,
+ Type? userRequestedEmbeddingType)
+ {
+ vectorProperty.EmbeddingGenerator = embeddingGenerator;
+
+ if (this.IsVectorPropertyTypeValid(vectorProperty.Type, out _))
+ {
+ if (userRequestedEmbeddingType is not null && userRequestedEmbeddingType != vectorProperty.Type)
+ {
+ throw new InvalidOperationException(VectorDataStrings.DifferentEmbeddingTypeSpecifiedForNativelySupportedType(vectorProperty, userRequestedEmbeddingType));
+ }
+
+ vectorProperty.EmbeddingType = vectorProperty.Type;
+
+ // Even for native types, if an embedding generator is configured, resolve the dispatcher
+ // so that search can convert arbitrary inputs (e.g. string) to embeddings.
+ if (embeddingGenerator is not null)
+ {
+ vectorProperty.EmbeddingGenerationDispatcher = this.ResolveSearchOnlyEmbeddingHandler(vectorProperty, embeddingGenerator);
+ }
+ }
+ else if (embeddingGenerator is not null)
+ {
+ // The property type isn't a valid embedding type, but an embedding generator is configured.
+ // Try to resolve the embedding type from it: if the configured generator supports translating the input type (e.g. string) to
+ // an output type supported by the provider, we set that as the embedding type.
+ // If this fails, EmbeddingType remains null and we defer the error to the validation phase.
+ var (embeddingType, handler) = this.ResolveEmbeddingType(vectorProperty, embeddingGenerator, userRequestedEmbeddingType);
+ vectorProperty.EmbeddingType = embeddingType;
+ vectorProperty.EmbeddingGenerationDispatcher = handler;
+ }
+
+ // If the property type isn't valid and there's no embedding generator, that's an error.
+ // But we throw later, in validation, to allow for provider customization to correct this invalid state after this step.
+ }
+
///
/// Extension hook for connectors to be able to customize the model.
///
@@ -628,19 +606,4 @@ protected virtual void ValidateKeyProperty(KeyPropertyModel keyProperty)
/// Validates that the .NET type for a vector property is supported by the provider.
///
protected abstract bool IsVectorPropertyTypeValid(Type type, [NotNullWhen(false)] out string? supportedTypes);
-
- [RequiresUnreferencedCode("This record creator is incompatible with trimming and is only used in non-trimming compatible codepaths")]
- private sealed class ActivatorBasedRecordCreator : IRecordCreator
- {
- public TRecord Create()
- => Activator.CreateInstance() ?? throw new InvalidOperationException($"Failed to instantiate record of type '{typeof(TRecord).Name}'.");
- }
-
- private sealed class DynamicRecordCreator : IRecordCreator
- {
- public TRecord Create()
- => typeof(TRecord) == typeof(Dictionary)
- ? (TRecord)(object)new Dictionary()
- : throw new UnreachableException($"Dynamic record creator only supports Dictionary, but got {typeof(TRecord).Name}.");
- }
}
diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/DataPropertyModel.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/DataPropertyModel.cs
index b2d800245054..da03ddbd5e1c 100644
--- a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/DataPropertyModel.cs
+++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/DataPropertyModel.cs
@@ -10,7 +10,7 @@ namespace Microsoft.Extensions.VectorData.ProviderServices;
/// This is an internal support type meant for use by connectors only and not by applications.
///
[Experimental("MEVD9001")]
-public class DataPropertyModel(string modelName, Type type) : PropertyModel(modelName, type)
+public class DataPropertyModel(string modelName, Type type) : PropertyModel(modelName, type), IDataPropertyModel
{
///
/// Gets or sets a value indicating whether this data property is indexed.
diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/Filter/FilterTranslatorBase.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/Filter/FilterTranslatorBase.cs
index ad847b34b1d1..7c4655a048da 100644
--- a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/Filter/FilterTranslatorBase.cs
+++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/Filter/FilterTranslatorBase.cs
@@ -109,7 +109,7 @@ protected static bool TryMatchContains(
/// The expression to bind.
/// When successful, the property model that was bound.
/// if the expression was successfully bound to a property; otherwise, .
- protected virtual bool TryBindProperty(Expression expression, [NotNullWhen(true)] out PropertyModel? propertyModel)
+ protected virtual bool TryBindProperty(Expression expression, [NotNullWhen(true)] out IPropertyModel? propertyModel)
{
var unwrappedExpression = expression;
while (unwrappedExpression is UnaryExpression { NodeType: ExpressionType.Convert } convert)
diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IDataPropertyModel.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IDataPropertyModel.cs
new file mode 100644
index 000000000000..cf96acfcc689
--- /dev/null
+++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IDataPropertyModel.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.Extensions.VectorData.ProviderServices;
+
+///
+/// Represents a read-only view of a data property on a vector store record.
+/// This is an internal support type meant for use by connectors only and not by applications.
+///
+[Experimental("MEVD9001")]
+public interface IDataPropertyModel : IPropertyModel
+{
+ ///
+ /// Gets a value indicating whether this data property is indexed.
+ ///
+ bool IsIndexed { get; }
+
+ ///
+ /// Gets a value indicating whether this data property is indexed for full-text search.
+ ///
+ bool IsFullTextIndexed { get; }
+}
diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IKeyPropertyModel.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IKeyPropertyModel.cs
new file mode 100644
index 000000000000..69e34bd99c81
--- /dev/null
+++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IKeyPropertyModel.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.Extensions.VectorData.ProviderServices;
+
+///
+/// Represents a read-only view of a key property on a vector store record.
+/// This is an internal support type meant for use by connectors only and not by applications.
+///
+[Experimental("MEVD9001")]
+public interface IKeyPropertyModel : IPropertyModel
+{
+ ///
+ /// Gets whether this key property's value is auto-generated or not.
+ ///
+ bool IsAutoGenerated { get; }
+
+ ///
+ /// Gets the name that the JSON serializer will produce for this key property.
+ ///
+ string? SerializedKeyName { get; }
+}
diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IPropertyModel.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IPropertyModel.cs
new file mode 100644
index 000000000000..c2d6a6ce536a
--- /dev/null
+++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IPropertyModel.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+
+namespace Microsoft.Extensions.VectorData.ProviderServices;
+
+///
+/// Represents a read-only view of a property on a vector store record.
+/// This is an internal support type meant for use by connectors only and not by applications.
+///
+[Experimental("MEVD9001")]
+public interface IPropertyModel
+{
+ ///
+ /// Gets the model name of the property. If the property corresponds to a .NET property, this name is the name of that property.
+ ///
+ string ModelName { get; }
+
+ ///
+ /// Gets the storage name of the property. This is the name to which the property is mapped in the vector store.
+ ///
+ string StorageName { get; }
+
+ ///
+ /// Gets the CLR type of the property.
+ ///
+ Type Type { get; }
+
+ ///
+ /// Gets the reflection for the .NET property.
+ ///
+ ///
+ /// The reflection for the .NET property.
+ /// when using dynamic mapping.
+ ///
+ PropertyInfo? PropertyInfo { get; }
+
+ ///
+ /// Gets whether the property type is nullable.
+ ///
+ bool IsNullable { get; }
+
+ ///
+ /// Gets a dictionary of provider-specific annotations for this property.
+ ///
+ IReadOnlyDictionary? ProviderAnnotations { get; }
+
+ ///
+ /// Reads the property from the given , returning the value as an .
+ ///
+ object? GetValueAsObject(object record);
+
+ ///
+ /// Writes the property from the given , accepting the value to write as an .
+ ///
+ void SetValueAsObject(object record, object? value);
+
+ ///
+ /// Reads the property from the given .
+ ///
+ T GetValue(object record);
+
+ ///
+ /// Writes the property from the given .
+ ///
+ void SetValue(object record, T value);
+}
diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IRecordCreator.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IRecordCreator.cs
deleted file mode 100644
index 079659ce5bcb..000000000000
--- a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IRecordCreator.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-namespace Microsoft.Extensions.VectorData.ProviderServices;
-
-internal interface IRecordCreator
-{
- TRecord Create();
-}
diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IVectorPropertyModel.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IVectorPropertyModel.cs
new file mode 100644
index 000000000000..2580c0bf73b7
--- /dev/null
+++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/IVectorPropertyModel.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Extensions.VectorData.ProviderServices;
+
+///
+/// Represents a read-only view of a vector property on a vector store record.
+/// This is an internal support type meant for use by connectors only and not by applications.
+///
+[Experimental("MEVD9001")]
+public interface IVectorPropertyModel : IPropertyModel
+{
+ ///
+ /// Gets the number of dimensions that the vector has.
+ ///
+ int Dimensions { get; }
+
+ ///
+ /// Gets the kind of index to use.
+ ///
+ string? IndexKind { get; }
+
+ ///
+ /// Gets the distance function to use when comparing vectors.
+ ///
+ string? DistanceFunction { get; }
+
+ ///
+ /// Gets the type representing the embedding stored in the database.
+ ///
+ ///
+ /// This is guaranteed to be non-null after model building completes.
+ /// If is set, this is the output embedding type;
+ /// otherwise it is identical to .
+ ///
+ Type EmbeddingType { get; }
+
+ ///
+ /// Gets the embedding generator to use for this property.
+ ///
+ IEmbeddingGenerator? EmbeddingGenerator { get; }
+
+ ///
+ /// Gets the that was resolved for this property during model building.
+ ///
+ EmbeddingGenerationDispatcher? EmbeddingGenerationDispatcher { get; }
+
+ ///
+ /// Generates embeddings for the given , using the configured .
+ ///
+ Task> GenerateEmbeddingsAsync(IEnumerable values, CancellationToken cancellationToken);
+
+ ///
+ /// Generates a single embedding for the given , using the configured .
+ ///
+ Task GenerateEmbeddingAsync(object? value, CancellationToken cancellationToken);
+}
diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/KeyPropertyModel.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/KeyPropertyModel.cs
index 76f90822f7a5..a14382be0d2c 100644
--- a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/KeyPropertyModel.cs
+++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/KeyPropertyModel.cs
@@ -10,13 +10,21 @@ namespace Microsoft.Extensions.VectorData.ProviderServices;
/// This is an internal support type meant for use by connectors only and not by applications.
///
[Experimental("MEVD9001")]
-public class KeyPropertyModel(string modelName, Type type) : PropertyModel(modelName, type)
+public class KeyPropertyModel(string modelName, Type type) : PropertyModel(modelName, type), IKeyPropertyModel
{
///
/// Gets or sets whether this key property's value is auto-generated or not.
///
public bool IsAutoGenerated { get; set; }
+ ///
+ /// Gets or sets the name that the JSON serializer will produce for this key property.
+ /// This is needed for connectors that use an external JSON serializer combined with a reserved key storage name
+ /// (e.g. CosmosDB NoSQL uses "id"): the serializer produces a JSON object with the policy-transformed name, and
+ /// the connector needs to find and replace it with the reserved storage name.
+ ///
+ public string? SerializedKeyName { get; set; }
+
///
public override string ToString()
=> $"{this.ModelName} (Key, {this.Type.Name}{(this.IsAutoGenerated ? ", auto-generated" : "")})";
diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/PropertyModel.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/PropertyModel.cs
index 2815847a757d..3ca86c60e844 100644
--- a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/PropertyModel.cs
+++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/PropertyModel.cs
@@ -13,9 +13,11 @@ namespace Microsoft.Extensions.VectorData.ProviderServices;
/// This is an internal support type meant for use by connectors only and not by applications.
///
[Experimental("MEVD9001")]
-public abstract class PropertyModel(string modelName, Type type)
+public abstract class PropertyModel(string modelName, Type type) : IPropertyModel
{
private string? _storageName;
+ private Func? _getter;
+ private Action? _setter;
///
/// Gets or sets the model name of the property. If the property corresponds to a .NET property, this name is the name of that property.
@@ -31,15 +33,6 @@ public string StorageName
set => this._storageName = value;
}
- // See comment in VectorStoreJsonModelBuilder
- // TODO: Spend more time thinking about this, there may be a less hacky way to handle it.
-
- ///
- /// Gets or sets the temporary storage name for the property, for use during the serialization process by certain connectors.
- ///
- [Experimental("MEVD9001")]
- public string? TemporaryStorageName { get; set; }
-
///
/// Gets or sets the CLR type of the property.
///
@@ -62,6 +55,8 @@ public string StorageName
///
public Dictionary? ProviderAnnotations { get; set; }
+ IReadOnlyDictionary? IPropertyModel.ProviderAnnotations => this.ProviderAnnotations;
+
///
/// Gets whether the property type is nullable. For value types, this is when the type is
/// . For reference types on .NET 6+, this uses NRT annotations via
@@ -93,79 +88,77 @@ public bool IsNullable
}
///
- /// Reads the property from the given , returning the value as an .
+ /// Configures the property accessors using a CLR for POCO mapping.
///
- public virtual object? GetValueAsObject(object record)
+ // TODO: Implement compiled delegates for better performance, #11122
+ // TODO: Implement source-generated accessors for NativeAOT, #10256
+ internal void ConfigurePocoAccessors(PropertyInfo propertyInfo)
{
- if (this.PropertyInfo is null)
+ this.PropertyInfo = propertyInfo;
+ this._getter = propertyInfo.GetValue;
+ this._setter = (record, value) =>
{
- if (record is Dictionary dictionary)
+ // If the value is null, no need to set the property (it's the CLR default)
+ if (value is not null)
{
- var value = dictionary.TryGetValue(this.ModelName, out var tempValue)
- ? tempValue
- : null;
-
- if (value is not null && value.GetType() != (Nullable.GetUnderlyingType(this.Type) ?? this.Type))
- {
- throw new InvalidCastException($"Property '{this.ModelName}' has a value of type '{value.GetType().Name}', but its configured type is '{this.Type.Name}'.");
- }
-
- return value;
+ propertyInfo.SetValue(record, value);
}
-
- throw new UnreachableException("Non-dynamic mapping but PropertyInfo is null.");
- }
-
- // We have a CLR property (non-dynamic POCO mapping)
-
- // TODO: Implement compiled delegates for better performance, #11122
- // TODO: Implement source-generated accessors for NativeAOT, #10256
-
- return this.PropertyInfo.GetValue(record);
+ };
}
///
- /// Writes the property from the given , accepting the value to write as an .
- /// s
- public virtual void SetValueAsObject(object record, object? value)
+ /// Configures the property accessors for dynamic mapping using .
+ ///
+ internal void ConfigureDynamicAccessors()
{
- if (this.PropertyInfo is null)
+ var modelName = this.ModelName;
+ var propertyType = this.Type;
+
+ this._getter = record =>
{
- if (record.GetType() == typeof(Dictionary))
+ var dictionary = (Dictionary)record;
+ var value = dictionary.TryGetValue(modelName, out var tempValue) ? tempValue : null;
+
+ if (value is not null && value.GetType() != (Nullable.GetUnderlyingType(propertyType) ?? propertyType))
{
- var dictionary = (Dictionary)record;
- dictionary[this.ModelName] = value;
- return;
+ throw new InvalidCastException($"Property '{modelName}' has a value of type '{value.GetType().Name}', but its configured type is '{propertyType.Name}'.");
}
- throw new UnreachableException("Non-dynamic mapping but ClrProperty is null.");
- }
+ return value;
+ };
- // We have a CLR property (non-dynamic POCO mapping)
+ this._setter = (record, value) => ((Dictionary)record)[modelName] = value;
+ }
- // TODO: Implement compiled delegates for better performance, #11122
- // TODO: Implement source-generated accessors for NativeAOT, #10256
+ ///
+ /// Reads the property from the given , returning the value as an .
+ ///
+ public object? GetValueAsObject(object record)
+ {
+ Debug.Assert(this._getter is not null, "Property accessors have not been configured.");
+ return this._getter!(record);
+ }
- // If the value is null, no need to set the property (it's the CLR default)
- if (value is not null)
- {
- this.PropertyInfo.SetValue(record, value);
- }
+ ///
+ /// Writes the property from the given , accepting the value to write as an .
+ ///
+ public void SetValueAsObject(object record, object? value)
+ {
+ Debug.Assert(this._setter is not null, "Property accessors have not been configured.");
+ this._setter!(record, value);
}
///
/// Reads the property from the given .
///
// TODO: actually implement the generic accessors to avoid boxing, and make use of them in connectors
- public virtual T GetValue(object record)
+ public T GetValue(object record)
=> (T)(object)this.GetValueAsObject(record)!;
///
/// Writes the property from the given .
- /// s
+ ///
// TODO: actually implement the generic accessors to avoid boxing, and make use of them in connectors
- public virtual void SetValue(object record, T value)
- {
- this.SetValueAsObject(record, value);
- }
+ public void SetValue(object record, T value)
+ => this.SetValueAsObject(record, value);
}
diff --git a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/VectorPropertyModel.cs b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/VectorPropertyModel.cs
index 0ba7b33ef0ea..4c80ce62d321 100644
--- a/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/VectorPropertyModel.cs
+++ b/dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/VectorPropertyModel.cs
@@ -16,7 +16,7 @@ namespace Microsoft.Extensions.VectorData.ProviderServices;
/// This is an internal support type meant for use by connectors only and not by applications.
///
[Experimental("MEVD9001")]
-public class VectorPropertyModel(string modelName, Type type) : PropertyModel(modelName, type)
+public class VectorPropertyModel(string modelName, Type type) : PropertyModel(modelName, type), IVectorPropertyModel
{
private int _dimensions;
@@ -62,10 +62,12 @@ public int Dimensions
///
/// Gets or sets the type representing the embedding stored in the database if is set.
- /// Otherwise, this property is identical to .
+ /// Otherwise, this property is identical to .
///
- // TODO: sort out the nullability story here: EmbeddingType must be non-null after model building is complete, but can be null during
- // model building as we're figuring things out (i.e. introduce a provider-facing interface where the property is non-nullable).
+ ///
+ /// This property may be during model building while the embedding type is being resolved,
+ /// but is guaranteed to be non-null after building completes (validation ensures this).
+ ///
[AllowNull]
public Type EmbeddingType { get; set; } = null!;
diff --git a/dotnet/src/VectorData/Weaviate/WeaviateCollection.cs b/dotnet/src/VectorData/Weaviate/WeaviateCollection.cs
index 900c865c6396..600e993c4b5b 100644
--- a/dotnet/src/VectorData/Weaviate/WeaviateCollection.cs
+++ b/dotnet/src/VectorData/Weaviate/WeaviateCollection.cs
@@ -356,7 +356,7 @@ public override async IAsyncEnumerable> SearchAsync<
}
}
- private static async ValueTask> GetSearchVectorAsync(TInput searchValue, VectorPropertyModel vectorProperty, CancellationToken cancellationToken)
+ private static async ValueTask> GetSearchVectorAsync(TInput searchValue, IVectorPropertyModel vectorProperty, CancellationToken cancellationToken)
where TInput : notnull
=> searchValue switch
{
diff --git a/dotnet/src/VectorData/Weaviate/WeaviateMapper.cs b/dotnet/src/VectorData/Weaviate/WeaviateMapper.cs
index 730199f0a87c..f365fac75f21 100644
--- a/dotnet/src/VectorData/Weaviate/WeaviateMapper.cs
+++ b/dotnet/src/VectorData/Weaviate/WeaviateMapper.cs
@@ -175,7 +175,7 @@ public TRecord MapFromStorageToDataModel(JsonObject storageModel, bool includeVe
return record;
- static void PopulateVectorProperty(TRecord record, object? value, VectorPropertyModel property)
+ static void PopulateVectorProperty(TRecord record, object? value, IVectorPropertyModel property)
{
switch (value)
{
diff --git a/dotnet/src/VectorData/Weaviate/WeaviateQueryBuilder.cs b/dotnet/src/VectorData/Weaviate/WeaviateQueryBuilder.cs
index 58aee493a149..f1694793b32d 100644
--- a/dotnet/src/VectorData/Weaviate/WeaviateQueryBuilder.cs
+++ b/dotnet/src/VectorData/Weaviate/WeaviateQueryBuilder.cs
@@ -122,8 +122,8 @@ public static string BuildHybridSearchQuery(
string keywords,
string collectionName,
CollectionModel model,
- VectorPropertyModel vectorProperty,
- DataPropertyModel textProperty,
+ IVectorPropertyModel vectorProperty,
+ IDataPropertyModel textProperty,
JsonSerializerOptions jsonSerializerOptions,
HybridSearchOptions searchOptions,
bool hasNamedVectors)
diff --git a/dotnet/test/VectorData/PgVector.UnitTests/PostgresPropertyMappingTests.cs b/dotnet/test/VectorData/PgVector.UnitTests/PostgresPropertyMappingTests.cs
index 64dafc41de81..56beee094119 100644
--- a/dotnet/test/VectorData/PgVector.UnitTests/PostgresPropertyMappingTests.cs
+++ b/dotnet/test/VectorData/PgVector.UnitTests/PostgresPropertyMappingTests.cs
@@ -93,7 +93,7 @@ public void GetPropertyValueReturnsCorrectNullableValue()
public void GetIndexInfoReturnsCorrectValues()
{
// Arrange
- List vectorProperties =
+ List vectorProperties =
[
new VectorPropertyModel("vector1", typeof(ReadOnlyMemory?)) { IndexKind = IndexKind.Hnsw, Dimensions = 1000 },
new VectorPropertyModel("vector2", typeof(ReadOnlyMemory?)) { IndexKind = IndexKind.Flat, Dimensions = 3000 },
diff --git a/dotnet/test/VectorData/Redis.UnitTests/RedisCollectionCreateMappingTests.cs b/dotnet/test/VectorData/Redis.UnitTests/RedisCollectionCreateMappingTests.cs
index 8d218a3566b4..d56c05b59d69 100644
--- a/dotnet/test/VectorData/Redis.UnitTests/RedisCollectionCreateMappingTests.cs
+++ b/dotnet/test/VectorData/Redis.UnitTests/RedisCollectionCreateMappingTests.cs
@@ -21,7 +21,7 @@ public class RedisCollectionCreateMappingTests
public void MapToSchemaCreatesSchema(bool useDollarPrefix)
{
// Arrange.
- PropertyModel[] properties =
+ IPropertyModel[] properties =
[
new KeyPropertyModel("Key", typeof(string)),
diff --git a/dotnet/test/VectorData/SqliteVec.UnitTests/SqlitePropertyMappingTests.cs b/dotnet/test/VectorData/SqliteVec.UnitTests/SqlitePropertyMappingTests.cs
index fef4557ec29f..1899612fa628 100644
--- a/dotnet/test/VectorData/SqliteVec.UnitTests/SqlitePropertyMappingTests.cs
+++ b/dotnet/test/VectorData/SqliteVec.UnitTests/SqlitePropertyMappingTests.cs
@@ -34,7 +34,7 @@ public void MapVectorForStorageModelReturnsByteArray()
public void GetColumnsReturnsCollectionOfColumns(bool data)
{
// Arrange
- var properties = new List()
+ var properties = new List()
{
new KeyPropertyModel("Key", typeof(string)) { StorageName = "Key" },
new DataPropertyModel("Data", typeof(int)) { StorageName = "my_data", IsIndexed = true },