Skip to content

Commit 5f282a9

Browse files
rojiCopilot
andauthored
Refactor CollectionModel/builder and introduce read-only property interfaces (#13699)
## Changes * **Extract `ConfigureVectorPropertyEmbedding()`**: Deduplicated embedding resolution logic that was repeated across `ProcessTypeProperties` and `ProcessRecordDefinition`. * **Replace `IRecordCreator` with `Func<object>`**: Removed `IRecordCreator` interface and two implementing classes (`ActivatorBasedRecordCreator`, `DynamicRecordCreator`), replacing them with simple lambdas. * **Rename `TemporaryStorageName` → `SerializedKeyName`**: Moved from `PropertyModel` base to `KeyPropertyModel` where it belongs (only used by CosmosNoSql for key property JSON serializer name remapping). * **Delegate-based property accessors**: Replaced virtual `GetValueAsObject`/`SetValueAsObject` overrides with delegate fields (`_getter`/`_setter`), configured via `ConfigurePocoAccessors()`/`ConfigureDynamicAccessors()`. Converted null-coalescing throws to `Debug.Assert`. * **Improved xmldoc** on `VectorPropertyModel.EmbeddingType` explaining the `[AllowNull]` invariant. * **Introduce read-only interfaces** (`IPropertyModel`, `IKeyPropertyModel`, `IDataPropertyModel`, `IVectorPropertyModel`): `CollectionModel` now exposes `IReadOnlyList<IVectorPropertyModel>` etc., giving providers an immutable view post-build. All provider code updated to consume interface types. Note that the last is a non-trivial breaking change for providers (not users), which I think is fine (note that the provider-facing APIs are flagged as [Experimental]). --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9d120bc commit 5f282a9

File tree

10 files changed

+284
-325
lines changed

10 files changed

+284
-325
lines changed

dotnet/Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
<PackageVersion Include="PdfPig" Version="0.1.13" />
103103
<PackageVersion Include="Pinecone.Client" Version="3.1.0" />
104104
<PackageVersion Include="Prompty.Core" Version="0.2.3-beta" />
105-
<PackageVersion Include="Scriban" Version="6.6.0" />
105+
<PackageVersion Include="Scriban" Version="7.0.3" />
106106
<PackageVersion Include="PuppeteerSharp" Version="20.2.5" />
107107
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="10.0.2" />
108108
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.15.0" />

dotnet/src/InternalUtilities/connectors/Memory/MongoDB/MongoModelBuilder.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,14 @@ internal class MongoModelBuilder() : CollectionModelBuilder(s_validationOptions)
2727
UsesExternalSerializer = true,
2828
};
2929

30-
[RequiresUnreferencedCode("Traverses the CLR type's properties with reflection, so not compatible with trimming")]
31-
protected override void ProcessTypeProperties(Type type, VectorStoreCollectionDefinition? definition)
30+
protected override void ProcessProperty(PropertyInfo? clrProperty, VectorStoreProperty? definitionProperty, Type? type)
3231
{
33-
base.ProcessTypeProperties(type, definition);
32+
base.ProcessProperty(clrProperty, definitionProperty, type);
3433

35-
foreach (var property in this.Properties)
34+
if (clrProperty?.GetCustomAttribute<BsonElementAttribute>() is { } bsonElementAttribute
35+
&& this.PropertyMap.TryGetValue(clrProperty.Name, out var property))
3636
{
37-
if (property.PropertyInfo?.GetCustomAttribute<BsonElementAttribute>() is { } bsonElementAttribute)
38-
{
39-
property.StorageName = bsonElementAttribute.ElementName;
40-
}
37+
property.StorageName = bsonElementAttribute.ElementName;
4138
}
4239
}
4340

dotnet/src/VectorData/CosmosNoSql/CosmosNoSqlMapper.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ public JsonObject MapFromDataToStorageModel(TRecord dataModel, int recordIndex,
4141

4242
// The key property in Azure CosmosDB NoSQL is always named 'id'.
4343
// But the external JSON serializer used just above isn't aware of that, and will produce a JSON object with another name, taking into
44-
// account e.g. naming policies. TemporaryStorageName gets populated in the model builder - containing that name - once VectorStoreModelBuildingOptions.ReservedKeyPropertyName is set
45-
RenameJsonProperty(jsonObject, this._keyProperty.TemporaryStorageName!, CosmosNoSqlConstants.ReservedKeyPropertyName);
44+
// account e.g. naming policies. SerializedKeyName gets populated in the model builder - containing that name - once VectorStoreModelBuildingOptions.ReservedKeyPropertyName is set
45+
RenameJsonProperty(jsonObject, this._keyProperty.SerializedKeyName!, CosmosNoSqlConstants.ReservedKeyPropertyName);
4646

4747
// Go over the vector properties; inject any generated embeddings to overwrite the JSON serialized above.
4848
// Also, for Embedding<T> properties we also need to overwrite with a simple array (since Embedding<T> gets serialized as a complex object).
@@ -116,7 +116,7 @@ public JsonObject MapFromDataToStorageModel(TRecord dataModel, int recordIndex,
116116
public TRecord MapFromStorageToDataModel(JsonObject storageModel, bool includeVectors)
117117
{
118118
// See above comment.
119-
RenameJsonProperty(storageModel, CosmosNoSqlConstants.ReservedKeyPropertyName, this._keyProperty.TemporaryStorageName!);
119+
RenameJsonProperty(storageModel, CosmosNoSqlConstants.ReservedKeyPropertyName, this._keyProperty.SerializedKeyName!);
120120

121121
foreach (var vectorProperty in this._model.VectorProperties)
122122
{

dotnet/src/VectorData/SqliteVec/SqlitePropertyMapping.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public static List<SqliteColumn> GetColumns(IReadOnlyList<PropertyModel> propert
6262
else
6363
{
6464
// The Key column in included in both Vector and Data tables.
65-
Debug.Assert(property is KeyPropertyModel, "property is VectorStoreRecordKeyPropertyModel");
65+
Debug.Assert(property is KeyPropertyModel, "property is not a KeyPropertyModel");
6666

6767
propertyType = GetStorageDataPropertyType(property);
6868
isPrimary = true;

dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionJsonModelBuilder.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,11 @@ protected override void Customize()
8989

9090
if (keyPropertyWithReservedName)
9191
{
92-
// Somewhat hacky:
9392
// Some providers (Weaviate, Cosmos NoSQL) have a fixed, reserved storage name for keys (id), and at the same time use an external
9493
// JSON serializer to serialize the entire user POCO. Since the serializer is unaware of the reserved storage name, it will produce
9594
// a storage name as usual, based on the .NET property's name, possibly with a naming policy applied to it. The connector then needs
9695
// to look that up and replace with the reserved name.
97-
// So we store the policy-transformed name, as StorageName contains the reserved name.
98-
property.TemporaryStorageName = storageName;
96+
((KeyPropertyModel)property).SerializedKeyName = storageName;
9997
}
10098
else
10199
{

dotnet/src/VectorData/VectorData.Abstractions/ProviderServices/CollectionModel.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ namespace Microsoft.Extensions.VectorData.ProviderServices;
1919
public sealed class CollectionModel
2020
{
2121
private readonly Type _recordType;
22-
private readonly IRecordCreator _recordCreator;
22+
private readonly Func<object> _recordFactory;
2323

2424
private KeyPropertyModel? _singleKeyProperty;
2525
private VectorPropertyModel? _singleVectorProperty;
@@ -57,14 +57,14 @@ public sealed class CollectionModel
5757

5858
internal CollectionModel(
5959
Type recordType,
60-
IRecordCreator recordCreator,
60+
Func<object> recordFactory,
6161
IReadOnlyList<KeyPropertyModel> keyProperties,
6262
IReadOnlyList<DataPropertyModel> dataProperties,
6363
IReadOnlyList<VectorPropertyModel> vectorProperties,
6464
IReadOnlyDictionary<string, PropertyModel> propertyMap)
6565
{
6666
this._recordType = recordType;
67-
this._recordCreator = recordCreator;
67+
this._recordFactory = recordFactory;
6868

6969
this.KeyProperties = keyProperties;
7070
this.DataProperties = dataProperties;
@@ -97,7 +97,7 @@ public TRecord CreateRecord<TRecord>()
9797
{
9898
Debug.Assert(typeof(TRecord) == this._recordType, "Type mismatch between record type and model type.");
9999

100-
return this._recordCreator.Create<TRecord>();
100+
return (TRecord)this._recordFactory();
101101
}
102102

103103
/// <summary>

0 commit comments

Comments
 (0)