ITADN

VectorIndexPath: Fixes IndexingSearchListSize/QuantizationByteSize setters emitting spurious 0 on wire

#5921Openananth7592 创建于 2026-06-03
bugneeds-investigation
A
ananth7592commented
## Summary `VectorIndexPath.IndexingSearchListSize` and `VectorIndexPath.QuantizationByteSize` are declared as public `int` over a private `Nullable<int>` backing field decorated with `[JsonProperty(... NullValueHandling = NullValueHandling.Ignore)]`. The intent is clear: if the user never sets the property, the field stays `null` and the JSON serializer omits it from the wire body. This works for the direct construction path. It breaks the moment anything assigns the property programmatically, because the setter cannot distinguish "never set" from "set to 0": ```csharp // VectorIndexPath.cs public int IndexingSearchListSize { get => this.indexingSearchListSizeInternal ?? 0; set => this.indexingSearchListSizeInternal = value; // 0 → boxed 0, not null } ``` Any mapper, AutoMapper profile, `with`-expression copy, or hand-rolled property-by-property mapping that touches every public property will flip the backing field from `null` to `0`. Newtonsoft then faithfully emits `"indexingSearchListSize": 0` (and similarly for `quantizationByteSize`). The Cosmos service rejects the request: ``` Status: 400 BadRequest Substatus: 0 Message: IndexingSearchListSize is not allowed for Index Type::flat ``` Even on index types where the property *is* legal, `0` is out of the documented ranges (`IndexingSearchListSize`: 25–500, `QuantizationByteSize`: 1–512), so the value is never meaningful. This was hit by an internal customer (Intune) on SDK 3.59.0 when calling `ReplaceContainerAsync` with a Flat vector index. Their root cause is upstream object mapping that writes every property; the SDK has no defense against it. ## Proposed fix Treat `0` as "clear" in both setters, since `0` is never a valid value: ```csharp public int IndexingSearchListSize { get => this.indexingSearchListSizeInternal ?? 0; set => this.indexingSearchListSizeInternal = value == 0 ? (int?)null : value; } public int QuantizationByteSize { get => this.quantizationByteSizeInternal ?? 0; set => this.quantizationByteSizeInternal = value == 0 ? (int?)null : value; } ``` - No public API change. - No behavior change for any caller setting a valid value (25–500 / 1–512). - Eliminates the failure mode for callers that go through a mapper. ## Repro Construct a `VectorIndexPath`, copy it via AutoMapper or `with`-expression (or just call `path.IndexingSearchListSize = 0` explicitly), then serialize via the SDK's internal Newtonsoft serializer: ``` {"indexingSearchListSize":0,"quantizationByteSize":0,"path":"/v","type":"flat"} ``` Send to the service in a `Replace`/`Create` container call with `Type = Flat` → 400. ## Tests to add - `IndexingSearchListSize = 0` produces a wire body that does **not** contain `indexingSearchListSize`. - `QuantizationByteSize = 0` produces a wire body that does **not** contain `quantizationByteSize`. - Valid values (e.g. 100, 64) still round-trip correctly. - Round-trip via Newtonsoft of a value written as `0` then read back returns `0` (the getter contract is unchanged). ## Notes The same shape exists on any other `public int` over `private int?` field anywhere in the public surface — worth a quick audit while we're in there.
0 条评论