From cd988e01ddf3489761a45478759a4ac7f0245ecd Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Mon, 19 Jan 2026 11:39:03 +0300 Subject: [PATCH 1/6] Add ShouldReadPropertyAsStream API to allow access to property annotations (#3477) * Add ShouldReadPropertyAsStream and depracate ReadAsStreamFunc --------- Co-authored-by: John Gathogo --- .../ODataMessageReaderSettings.cs | 16 ++ .../ODataPropertyStreamingContext.cs | 44 +++++ .../JsonLight/ODataJsonLightReaderTests.cs | 183 +++++++++++++++++- 3 files changed, 237 insertions(+), 6 deletions(-) create mode 100644 src/Microsoft.OData.Core/ODataPropertyStreamingContext.cs diff --git a/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs b/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs index e0e8aa5479..aefb8bcf6f 100644 --- a/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs +++ b/src/Microsoft.OData.Core/ODataMessageReaderSettings.cs @@ -207,8 +207,23 @@ public ODataMessageQuotas MessageQuotas /// Function returns: /// * True, to have the value streamed, otherwise false /// + [Obsolete("Use 'ShouldReadPropertyAsStream' instead, which provides access to property annotations. ReadAsStreamFunc will be removed in a future ODL 9 release.")] public Func ReadAsStreamFunc { get; set; } + /// + /// Func to evaluate whether a property should be read as a stream. This function provides access to property annotations, enabling + /// scenarios where the decision to read as stream depends on annotations. Note that IEdmProperty may be null when reading within a collection. + /// + /// + /// provides: + /// * Primitive type of the value being read, or null if unknown + /// * Whether the value being read is a collection + /// * The name of the property being read (null for values within a collection) + /// * The property being read (null for dynamic property or value within a collection) + /// * Custom property annotations (non-OData annotations) that have been read so far. + /// + public Func ShouldReadPropertyAsStream { get; set; } + /// /// Func to evaluate whether an annotation should be read or skipped by the reader. The func should return true if the annotation should /// be read and false if the annotation should be skipped. A null value indicates that all annotations should be skipped. @@ -299,6 +314,7 @@ private void CopyFrom(ODataMessageReaderSettings other) this.LibraryCompatibility = other.LibraryCompatibility; this.Version = other.Version; this.ReadAsStreamFunc = other.ReadAsStreamFunc; + this.ShouldReadPropertyAsStream = other.ShouldReadPropertyAsStream; this.ArrayPool = other.ArrayPool; this.EnablePropertyNameCaseInsensitive = other.EnablePropertyNameCaseInsensitive; this.EnableUntypedCollections = other.EnableUntypedCollections; diff --git a/src/Microsoft.OData.Core/ODataPropertyStreamingContext.cs b/src/Microsoft.OData.Core/ODataPropertyStreamingContext.cs new file mode 100644 index 0000000000..aa3fd3fa2d --- /dev/null +++ b/src/Microsoft.OData.Core/ODataPropertyStreamingContext.cs @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.OData.Edm; + +namespace Microsoft.OData +{ + /// + /// Context information for reading an OData property + /// + public class ODataPropertyStreamingContext + { + /// + /// The primitive type of the property being read, or null if unknown. + /// + public IEdmPrimitiveType PrimitiveType { get; internal set; } + + /// + /// Indicates whether the value being read is a collection. + /// + public bool IsCollection { get; internal set; } + + /// + /// The name of the property being read (null for values within a collection) + /// + public string PropertyName { get; internal set; } + + /// + /// The EDM property being read (null for dynamic property or value within a collection) + /// + public IEdmProperty Property { get; internal set; } + + /// + /// The custom annotations associated with this property. + /// These are annotations that do not correspond to reserved OData annotations, + /// regardless of whether the optional "odata." prefix is present. + /// + public IEnumerable> CustomPropertyAnnotations { get; internal set; } + } +} diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightReaderTests.cs index a333a2ebf4..53940f7ed1 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightReaderTests.cs @@ -845,8 +845,7 @@ await SetupJsonLightReaderAndRunTestAsync( [Fact] public async Task ReadResourceWithStringPropertyReadAsStreamAsync() { - this.messageReaderSettings.ReadAsStreamFunc = - (primitiveType, isCollection, propertyName, edmProperty) => propertyName.Equals("Name"); + this.messageReaderSettings.ShouldReadPropertyAsStream = (context) => context.PropertyName.Equals("Name"); var payload = "{\"@odata.context\":\"http://tempuri.org/$metadata#Customers/$entity\"," + "\"Id\":1," + @@ -873,8 +872,7 @@ await SetupJsonLightReaderAndRunTestAsync( [Fact] public async Task ReadResourceWithStringPropertySetToNullReadAsStreamAsync() { - this.messageReaderSettings.ReadAsStreamFunc = - (primitiveType, isCollection, propertyName, edmProperty) => propertyName.Equals("Name"); + this.messageReaderSettings.ShouldReadPropertyAsStream = (context) => context.PropertyName.Equals("Name"); var payload = "{\"@odata.context\":\"http://tempuri.org/$metadata#Customers/$entity\"," + "\"Id\":1," + @@ -898,6 +896,180 @@ await SetupJsonLightReaderAndRunTestAsync( })); } + [Fact] + public void ReadDynamicPropertyWithCustomAnnotationAsStream() + { + this.messageReaderSettings.ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter("*"); + this.messageReaderSettings.ShouldReadPropertyAsStream = (context) => + { + // Check if the property has a custom annotation + foreach (var annotation in context.CustomPropertyAnnotations) + { + if (annotation.Key == "is.Large" && annotation.Value is bool isLarge && isLarge) + { + return true; + } + } + + return false; + }; + + var payload = "{\"@odata.context\":\"http://tempuri.org/$metadata#Customers/$entity\"," + + "\"Id\":1," + + "\"Name\":null," + + "\"LargeField@odata.type\":\"#Edm.String\"," + + "\"LargeField@is.Large\":true," + + "\"LargeField\":\"This is a large string value that should be streamed\"}"; + + SetupJsonReaderAndRunTest( + payload, + this.customerEntitySet, + this.customerEntityType, + (jsonReader) => JsonReaderUtils.DoRead( + jsonReader, + verifyResourceAction: (resource) => + { + Assert.NotNull(resource); + // Id should be present, but LargeField should have been streamed (not in properties) + var properties = resource.Properties.OfType().ToArray(); + Assert.Equal(2, properties.Count()); + Assert.Equal("Id", properties[0].Name); + Assert.Equal("Name", properties[1].Name); + }, + verifyTextStreamAction: async (textReader) => + { + var result = textReader.ReadToEnd(); + Assert.Equal("This is a large string value that should be streamed", result); + })); + } + + [Fact] + public async Task ReadDynamicPropertyWithCustomAnnotationAsStreamAsync() + { + this.messageReaderSettings.ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter("*"); + this.messageReaderSettings.ShouldReadPropertyAsStream = (context) => + { + // Check if the property has a custom annotation + foreach (var annotation in context.CustomPropertyAnnotations) + { + if (annotation.Key == "is.Large" && annotation.Value is bool isLarge && isLarge) + { + return true; + } + } + + return false; + }; + + var payload = "{\"@odata.context\":\"http://tempuri.org/$metadata#Customers/$entity\"," + + "\"Id\":1," + + "\"LargeField@odata.type\":\"#Edm.String\"," + + "\"LargeField@is.Large\":true," + + "\"LargeField\":\"This is a large string value that should be streamed\"}"; + + await SetupJsonReaderAndRunTestAsync( + payload, + this.customerEntitySet, + this.customerEntityType, + (jsonReader) => JsonReaderUtils.DoReadAsync( + jsonReader, + verifyResourceAction: (resource) => + { + Assert.NotNull(resource); + // Id should be present, but LargeField should have been streamed (not in properties) + var properties = resource.Properties.OfType().ToArray(); + Assert.Single(properties); + Assert.Equal("Id", properties[0].Name); + }, + verifyTextStreamAction: async (textReader) => + { + var result = await textReader.ReadToEndAsync(); + Assert.Equal("This is a large string value that should be streamed", result); + })); + } + + [Fact] + public void ReadDynamicPropertyWithoutTheCustomAnnotationNotStreamed() + { + this.messageReaderSettings.ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter("*"); + this.messageReaderSettings.ShouldReadPropertyAsStream = (context) => + { + foreach (var annotation in context.CustomPropertyAnnotations) + { + if (annotation.Key == "is.Large" && annotation.Value is bool isLarge && isLarge) + { + return true; + } + } + + return false; + }; + + var payload = "{\"@odata.context\":\"http://tempuri.org/$metadata#Customers/$entity\"," + + "\"Id\":1," + + "\"SmallField\":\"This is a small string value that should NOT be streamed\"}"; + + SetupJsonReaderAndRunTest( + payload, + this.customerEntitySet, + this.customerEntityType, + (jsonReader) => JsonReaderUtils.DoRead( + jsonReader, + verifyResourceAction: (resource) => + { + Assert.NotNull(resource); + // Both Id and SmallField should be in properties (not streamed) + var properties = resource.Properties.OfType().ToArray(); + Assert.Equal(2, properties.Length); + Assert.Equal("Id", properties[0].Name); + Assert.Equal("SmallField", properties[1].Name); + // Dynamic properties without type annotation are read as ODataUntypedValue + var untypedValue = Assert.IsType(properties[1].Value); + Assert.Contains("This is a small string value", untypedValue.RawValue); + })); + } + + [Fact] + public async Task ReadDynamicPropertyWithoutTheCustomAnnotationNotStreamedAsync() + { + this.messageReaderSettings.ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter("*"); + this.messageReaderSettings.ShouldReadPropertyAsStream = (context) => + { + foreach (var annotation in context.CustomPropertyAnnotations) + { + if (annotation.Key == "is.Large" && annotation.Value is bool isLarge && isLarge) + { + return true; + } + } + + return false; + }; + + var payload = "{\"@odata.context\":\"http://tempuri.org/$metadata#Customers/$entity\"," + + "\"Id\":1," + + "\"SmallField\":\"This is a small string value that should NOT be streamed\"}"; + + await SetupJsonReaderAndRunTestAsync( + payload, + this.customerEntitySet, + this.customerEntityType, + (jsonReader) => JsonReaderUtils.DoReadAsync( + jsonReader, + verifyResourceAction: (resource) => + { + Assert.NotNull(resource); + // Both Id and SmallField should be in properties (not streamed) + var properties = resource.Properties.OfType().ToArray(); + Assert.Equal(2, properties.Length); + Assert.Equal("Id", properties[0].Name); + Assert.Equal("SmallField", properties[1].Name); + // Dynamic properties without type annotation are read as ODataUntypedValue + var untypedValue = Assert.IsType(properties[1].Value); + Assert.Contains("This is a small string value", untypedValue.RawValue); + })); + } + [Fact] public async Task ReadEntityReferenceLinkInRequestPayloadAsync() { @@ -1433,8 +1605,7 @@ await SetupJsonLightReaderAndRunTestAsync( [Fact] public async Task ReadStringCollectionAsStreamAsync() { - this.messageReaderSettings.ReadAsStreamFunc = - (primitiveType, isCollection, propertyName, edmProperty) => true; + this.messageReaderSettings.ShouldReadPropertyAsStream = (context) => true; var payload = "{\"@odata.context\":\"http://tempuri.org/$metadata#Customers/$entity\"," + "\"Id\":1," + From 98d5653ab9a568883bdadd71ad2254937252f800 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Mon, 19 Jan 2026 19:22:06 +0300 Subject: [PATCH 2/6] Fix failing tasks --- .../JsonLight/ODataJsonLightReader.cs | 38 +++++++- .../ODataJsonLightResourceDeserializer.cs | 59 +++++++++--- .../PublicAPI/net45/PublicAPI.Unshipped.txt | 9 ++ .../netcoreapp3.1/PublicAPI.Unshipped.txt | 9 ++ .../netstandard1.1/PublicAPI.Unshipped.txt | 9 ++ .../netstandard2.0/PublicAPI.Unshipped.txt | 9 ++ .../JsonLight/ODataJsonLightReaderTests.cs | 96 +------------------ ...ODataJsonLightResourceDeserializerTests.cs | 2 +- .../ODataJsonLightStreamReadingTests.cs | 56 +++++------ .../Microsoft.OData.PublicApi.net45.bsl | 15 +++ ...crosoft.OData.PublicApi.netstandard1.1.bsl | 15 +++ ...crosoft.OData.PublicApi.netstandard2.0.bsl | 15 +++ tools/CustomMSBuild/Versioning.props | 2 +- 13 files changed, 194 insertions(+), 140 deletions(-) diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs index 549cfd97b6..987fad6519 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightReader.cs @@ -12,6 +12,7 @@ namespace Microsoft.OData.JsonLight using System.Collections.Generic; using System.Diagnostics; using System.IO; + using System.Linq; using System.Threading.Tasks; using Microsoft.OData.Edm; using Microsoft.OData.Evaluation; @@ -2253,17 +2254,14 @@ private void ReadNextResourceSetItem() private bool TryReadPrimitiveAsStream(IEdmType resourceType) { - Func readAsStream = this.jsonLightInputContext.MessageReaderSettings.ReadAsStreamFunc; - // Should stream primitive if // 1. Primitive is a stream value // 2. Primitive is a string or binary value (within an untyped or streamed collection) that the reader wants to read as a stream if ( (resourceType != null && resourceType.IsStream()) || (resourceType != null - && readAsStream != null && (resourceType.IsBinary() || resourceType.IsString()) - && readAsStream(resourceType as IEdmPrimitiveType, false, null, null))) + && ShouldReadPrimitiveCollectionItemAsStream(resourceType as IEdmPrimitiveType))) { if (resourceType == null || resourceType.IsUntyped()) { @@ -3831,6 +3829,38 @@ await this.ReadNextResourceSetItemAsync() return true; } + /// + /// Determines whether a primitive value within a collection should be read as a stream. + /// + /// The primitive type of the value, or null if unknown. + /// True if the primitive should be read as a stream; otherwise, false. + private bool ShouldReadPrimitiveCollectionItemAsStream(IEdmPrimitiveType primitiveType) + { + Func shouldReadAsStream = this.jsonLightInputContext.MessageReaderSettings.ShouldReadPropertyAsStream; + if (shouldReadAsStream != null) + { + ODataPropertyStreamingContext propertyReadingContext = new ODataPropertyStreamingContext + { + PrimitiveType = primitiveType, + IsCollection = false, + PropertyName = null, // No property name for primitives within collection + Property = null, // No EDM property for primitives within collection + CustomPropertyAnnotations = Enumerable.Empty>() // No custom annotations for primitives within collection + }; + + return shouldReadAsStream(propertyReadingContext); + } + + // Fallback to ReadAsStreamFunc + Func readAsStream = this.jsonLightInputContext.MessageReaderSettings.ReadAsStreamFunc; + if (readAsStream != null) + { + return readAsStream(primitiveType, false, null, null); + } + + return false; + } + #endregion private async methods #region scopes diff --git a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceDeserializer.cs b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceDeserializer.cs index c2388e24c7..dd0473e872 100644 --- a/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceDeserializer.cs +++ b/src/Microsoft.OData.Core/JsonLight/ODataJsonLightResourceDeserializer.cs @@ -1321,25 +1321,21 @@ private ODataJsonLightReaderNestedInfo TryReadAsStream(IODataJsonLightReaderReso isCollection = this.JsonReader.NodeType != JsonNodeType.PrimitiveValue; } - Func readAsStream = this.MessageReaderSettings.ReadAsStreamFunc; - // is the property a stream or a stream collection, // untyped collection, // or a binary or binary collection the client wants to read as a stream... if ( (primitiveType != null && (primitiveType.IsStream() || - (readAsStream != null - && (property == null || !property.IsKey()) // don't stream key properties + ((property == null || !property.IsKey()) // don't stream key properties && (primitiveType.IsBinary() || primitiveType.IsString() || isCollection)) - && readAsStream(primitiveType, isCollection, propertyName, property))) || + && ShouldReadPrimitiveValueAsStream(resourceState, primitiveType, isCollection, propertyName, property))) || (propertyType != null && isCollection && propertyType.Definition.AsElementType().IsUntyped()) || (propertyType == null && (isCollection || this.JsonReader.CanStream()) - && readAsStream != null - && readAsStream(null, isCollection, propertyName, property))) + && ShouldReadPrimitiveValueAsStream(resourceState, null, isCollection, propertyName, property))) { if (isCollection) { @@ -3589,25 +3585,21 @@ private async Task TryReadAsStreamAsync( isCollection = this.JsonReader.NodeType != JsonNodeType.PrimitiveValue; } - Func readAsStream = this.MessageReaderSettings.ReadAsStreamFunc; - // Is the property a stream or a stream collection, // untyped collection, // or a binary or binary collection the client wants to read as a stream... if ( (primitiveType != null && (primitiveType.IsStream() || - (readAsStream != null - && (property == null || !property.IsKey()) // don't stream key properties + ((property == null || !property.IsKey()) // don't stream key properties && (primitiveType.IsBinary() || primitiveType.IsString() || isCollection)) - && readAsStream(primitiveType, isCollection, propertyName, property))) || + && ShouldReadPrimitiveValueAsStream(resourceState, primitiveType, isCollection, propertyName, property))) || (propertyType != null && isCollection && propertyType.Definition.AsElementType().IsUntyped()) || (propertyType == null && (isCollection || await this.JsonReader.CanStreamAsync().ConfigureAwait(false)) - && readAsStream != null - && readAsStream(null, isCollection, propertyName, property))) + && ShouldReadPrimitiveValueAsStream(resourceState, null, isCollection, propertyName, property))) { if (isCollection) { @@ -4328,5 +4320,44 @@ await this.JsonReader.ReadEndArrayAsync() this.JsonReader.AssertNotBuffering(); this.AssertJsonCondition(JsonNodeType.Property, JsonNodeType.EndObject); } + + /// + /// Determines whether a primitive value should be read as a stream. + /// + /// The current resource state containing property annotations. + /// The primitive type of the property, or null if unknown. + /// Whether the property is a collection. + /// The name of the property. + /// The EDM property, or null for dynamic properties. + /// True if the property should be read as a stream; otherwise, false. + private bool ShouldReadPrimitiveValueAsStream(IODataJsonLightReaderResourceState resourceState, IEdmPrimitiveType primitiveType, bool isCollection, string propertyName, IEdmProperty property) + { + Func shouldReadAsStream = this.MessageReaderSettings.ShouldReadPropertyAsStream; + if (shouldReadAsStream != null) + { + IEnumerable> customPropertyAnnotations = resourceState?.PropertyAndAnnotationCollector?.GetCustomPropertyAnnotations(propertyName) + ?? Enumerable.Empty>(); + + ODataPropertyStreamingContext propertyReadingContext = new ODataPropertyStreamingContext + { + PrimitiveType = primitiveType, + IsCollection = isCollection, + PropertyName = propertyName, + Property = property, + CustomPropertyAnnotations = customPropertyAnnotations + }; + + return shouldReadAsStream(propertyReadingContext); + } + + // Fallback to ReadAsStreamFunc + Func readAsStream = this.MessageReaderSettings.ReadAsStreamFunc; + if (readAsStream != null) + { + return readAsStream(primitiveType, isCollection, propertyName, property); + } + + return false; + } } } diff --git a/src/Microsoft.OData.Core/PublicAPI/net45/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Core/PublicAPI/net45/PublicAPI.Unshipped.txt index e69de29bb2..1bcd67ca27 100644 --- a/src/Microsoft.OData.Core/PublicAPI/net45/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Core/PublicAPI/net45/PublicAPI.Unshipped.txt @@ -0,0 +1,9 @@ +Microsoft.OData.ODataMessageReaderSettings.ShouldReadPropertyAsStream.get -> System.Func +Microsoft.OData.ODataMessageReaderSettings.ShouldReadPropertyAsStream.set -> void +Microsoft.OData.ODataPropertyStreamingContext +Microsoft.OData.ODataPropertyStreamingContext.CustomPropertyAnnotations.get -> System.Collections.Generic.IEnumerable> +Microsoft.OData.ODataPropertyStreamingContext.IsCollection.get -> bool +Microsoft.OData.ODataPropertyStreamingContext.ODataPropertyStreamingContext() -> void +Microsoft.OData.ODataPropertyStreamingContext.PrimitiveType.get -> Microsoft.OData.Edm.IEdmPrimitiveType +Microsoft.OData.ODataPropertyStreamingContext.Property.get -> Microsoft.OData.Edm.IEdmProperty +Microsoft.OData.ODataPropertyStreamingContext.PropertyName.get -> string \ No newline at end of file diff --git a/src/Microsoft.OData.Core/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Core/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt index e69de29bb2..1bcd67ca27 100644 --- a/src/Microsoft.OData.Core/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Core/PublicAPI/netcoreapp3.1/PublicAPI.Unshipped.txt @@ -0,0 +1,9 @@ +Microsoft.OData.ODataMessageReaderSettings.ShouldReadPropertyAsStream.get -> System.Func +Microsoft.OData.ODataMessageReaderSettings.ShouldReadPropertyAsStream.set -> void +Microsoft.OData.ODataPropertyStreamingContext +Microsoft.OData.ODataPropertyStreamingContext.CustomPropertyAnnotations.get -> System.Collections.Generic.IEnumerable> +Microsoft.OData.ODataPropertyStreamingContext.IsCollection.get -> bool +Microsoft.OData.ODataPropertyStreamingContext.ODataPropertyStreamingContext() -> void +Microsoft.OData.ODataPropertyStreamingContext.PrimitiveType.get -> Microsoft.OData.Edm.IEdmPrimitiveType +Microsoft.OData.ODataPropertyStreamingContext.Property.get -> Microsoft.OData.Edm.IEdmProperty +Microsoft.OData.ODataPropertyStreamingContext.PropertyName.get -> string \ No newline at end of file diff --git a/src/Microsoft.OData.Core/PublicAPI/netstandard1.1/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Core/PublicAPI/netstandard1.1/PublicAPI.Unshipped.txt index e69de29bb2..1bcd67ca27 100644 --- a/src/Microsoft.OData.Core/PublicAPI/netstandard1.1/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Core/PublicAPI/netstandard1.1/PublicAPI.Unshipped.txt @@ -0,0 +1,9 @@ +Microsoft.OData.ODataMessageReaderSettings.ShouldReadPropertyAsStream.get -> System.Func +Microsoft.OData.ODataMessageReaderSettings.ShouldReadPropertyAsStream.set -> void +Microsoft.OData.ODataPropertyStreamingContext +Microsoft.OData.ODataPropertyStreamingContext.CustomPropertyAnnotations.get -> System.Collections.Generic.IEnumerable> +Microsoft.OData.ODataPropertyStreamingContext.IsCollection.get -> bool +Microsoft.OData.ODataPropertyStreamingContext.ODataPropertyStreamingContext() -> void +Microsoft.OData.ODataPropertyStreamingContext.PrimitiveType.get -> Microsoft.OData.Edm.IEdmPrimitiveType +Microsoft.OData.ODataPropertyStreamingContext.Property.get -> Microsoft.OData.Edm.IEdmProperty +Microsoft.OData.ODataPropertyStreamingContext.PropertyName.get -> string \ No newline at end of file diff --git a/src/Microsoft.OData.Core/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Microsoft.OData.Core/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index e69de29bb2..1bcd67ca27 100644 --- a/src/Microsoft.OData.Core/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OData.Core/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,9 @@ +Microsoft.OData.ODataMessageReaderSettings.ShouldReadPropertyAsStream.get -> System.Func +Microsoft.OData.ODataMessageReaderSettings.ShouldReadPropertyAsStream.set -> void +Microsoft.OData.ODataPropertyStreamingContext +Microsoft.OData.ODataPropertyStreamingContext.CustomPropertyAnnotations.get -> System.Collections.Generic.IEnumerable> +Microsoft.OData.ODataPropertyStreamingContext.IsCollection.get -> bool +Microsoft.OData.ODataPropertyStreamingContext.ODataPropertyStreamingContext() -> void +Microsoft.OData.ODataPropertyStreamingContext.PrimitiveType.get -> Microsoft.OData.Edm.IEdmPrimitiveType +Microsoft.OData.ODataPropertyStreamingContext.Property.get -> Microsoft.OData.Edm.IEdmProperty +Microsoft.OData.ODataPropertyStreamingContext.PropertyName.get -> string \ No newline at end of file diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightReaderTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightReaderTests.cs index 53940f7ed1..2c6dc82243 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightReaderTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightReaderTests.cs @@ -896,53 +896,6 @@ await SetupJsonLightReaderAndRunTestAsync( })); } - [Fact] - public void ReadDynamicPropertyWithCustomAnnotationAsStream() - { - this.messageReaderSettings.ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter("*"); - this.messageReaderSettings.ShouldReadPropertyAsStream = (context) => - { - // Check if the property has a custom annotation - foreach (var annotation in context.CustomPropertyAnnotations) - { - if (annotation.Key == "is.Large" && annotation.Value is bool isLarge && isLarge) - { - return true; - } - } - - return false; - }; - - var payload = "{\"@odata.context\":\"http://tempuri.org/$metadata#Customers/$entity\"," + - "\"Id\":1," + - "\"Name\":null," + - "\"LargeField@odata.type\":\"#Edm.String\"," + - "\"LargeField@is.Large\":true," + - "\"LargeField\":\"This is a large string value that should be streamed\"}"; - - SetupJsonReaderAndRunTest( - payload, - this.customerEntitySet, - this.customerEntityType, - (jsonReader) => JsonReaderUtils.DoRead( - jsonReader, - verifyResourceAction: (resource) => - { - Assert.NotNull(resource); - // Id should be present, but LargeField should have been streamed (not in properties) - var properties = resource.Properties.OfType().ToArray(); - Assert.Equal(2, properties.Count()); - Assert.Equal("Id", properties[0].Name); - Assert.Equal("Name", properties[1].Name); - }, - verifyTextStreamAction: async (textReader) => - { - var result = textReader.ReadToEnd(); - Assert.Equal("This is a large string value that should be streamed", result); - })); - } - [Fact] public async Task ReadDynamicPropertyWithCustomAnnotationAsStreamAsync() { @@ -967,11 +920,11 @@ public async Task ReadDynamicPropertyWithCustomAnnotationAsStreamAsync() "\"LargeField@is.Large\":true," + "\"LargeField\":\"This is a large string value that should be streamed\"}"; - await SetupJsonReaderAndRunTestAsync( + await SetupJsonLightReaderAndRunTestAsync( payload, this.customerEntitySet, this.customerEntityType, - (jsonReader) => JsonReaderUtils.DoReadAsync( + (jsonReader) => DoReadAsync( jsonReader, verifyResourceAction: (resource) => { @@ -988,47 +941,6 @@ await SetupJsonReaderAndRunTestAsync( })); } - [Fact] - public void ReadDynamicPropertyWithoutTheCustomAnnotationNotStreamed() - { - this.messageReaderSettings.ShouldIncludeAnnotation = ODataUtils.CreateAnnotationFilter("*"); - this.messageReaderSettings.ShouldReadPropertyAsStream = (context) => - { - foreach (var annotation in context.CustomPropertyAnnotations) - { - if (annotation.Key == "is.Large" && annotation.Value is bool isLarge && isLarge) - { - return true; - } - } - - return false; - }; - - var payload = "{\"@odata.context\":\"http://tempuri.org/$metadata#Customers/$entity\"," + - "\"Id\":1," + - "\"SmallField\":\"This is a small string value that should NOT be streamed\"}"; - - SetupJsonReaderAndRunTest( - payload, - this.customerEntitySet, - this.customerEntityType, - (jsonReader) => JsonReaderUtils.DoRead( - jsonReader, - verifyResourceAction: (resource) => - { - Assert.NotNull(resource); - // Both Id and SmallField should be in properties (not streamed) - var properties = resource.Properties.OfType().ToArray(); - Assert.Equal(2, properties.Length); - Assert.Equal("Id", properties[0].Name); - Assert.Equal("SmallField", properties[1].Name); - // Dynamic properties without type annotation are read as ODataUntypedValue - var untypedValue = Assert.IsType(properties[1].Value); - Assert.Contains("This is a small string value", untypedValue.RawValue); - })); - } - [Fact] public async Task ReadDynamicPropertyWithoutTheCustomAnnotationNotStreamedAsync() { @@ -1050,11 +962,11 @@ public async Task ReadDynamicPropertyWithoutTheCustomAnnotationNotStreamedAsync( "\"Id\":1," + "\"SmallField\":\"This is a small string value that should NOT be streamed\"}"; - await SetupJsonReaderAndRunTestAsync( + await SetupJsonLightReaderAndRunTestAsync( payload, this.customerEntitySet, this.customerEntityType, - (jsonReader) => JsonReaderUtils.DoReadAsync( + (jsonReader) => DoReadAsync( jsonReader, verifyResourceAction: (resource) => { diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightResourceDeserializerTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightResourceDeserializerTests.cs index 7045bbb283..9e1bd286a3 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightResourceDeserializerTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/JsonLight/ODataJsonLightResourceDeserializerTests.cs @@ -642,7 +642,7 @@ await SetupJsonLightResourceSerializerAndRunReadResourceContextTestAsync( [Fact] public async Task ReadResourceContentWithPrimitivePropertyReadAsStreamAsync() { - this.messageReaderSettings.ReadAsStreamFunc = (primitiveType, isCollection, propertyName, edmProperty) => propertyName.Equals("Name"); + this.messageReaderSettings.ShouldReadPropertyAsStream = (context) => context.PropertyName.Equals("Name"); var payload = "{\"@odata.context\":\"http://tempuri.org/$metadata#Categories/$entity\"," + "\"Id\":1," + diff --git a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Streaming/ODataJsonLightStreamReadingTests.cs b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Streaming/ODataJsonLightStreamReadingTests.cs index 87821cbaa3..47ffc13c6d 100644 --- a/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Streaming/ODataJsonLightStreamReadingTests.cs +++ b/test/FunctionalTests/Microsoft.OData.Core.Tests/ScenarioTests/Streaming/ODataJsonLightStreamReadingTests.cs @@ -230,8 +230,8 @@ public void CanReadAllNonKeyPropertiesAsStream() ",\"comments\":[\"one\",\"two\",null]" ); - Func ShouldStream = - (IEdmPrimitiveType type, bool isCollection, string propertyName, IEdmProperty property) => + Func ShouldStream = + (ODataPropertyStreamingContext context) => { return true; }; @@ -282,10 +282,10 @@ public void CanStreamCollections() ",\"comments\":[\"one\",\"two\",null]" ); - Func ShouldStream = - (IEdmPrimitiveType type, bool isCollection, string propertyName, IEdmProperty property) => + Func ShouldStream = + (ODataPropertyStreamingContext context) => { - return isCollection; + return context.IsCollection; }; foreach (Variant variant in GetVariants(ShouldStream)) @@ -341,25 +341,25 @@ public void CanReadUntypedCollection() ",-10.5]" ); - Func StreamCollection = - (IEdmPrimitiveType type, bool isCollection, string propertyName, IEdmProperty property) => + Func StreamCollection = + (ODataPropertyStreamingContext context) => { - return isCollection; + return context.IsCollection; }; - Func StreamAll = - (IEdmPrimitiveType type, bool isCollection, string propertyName, IEdmProperty property) => + Func StreamAll = + (ODataPropertyStreamingContext context) => { return true; }; - Func StreamNone = - (IEdmPrimitiveType type, bool isCollection, string propertyName, IEdmProperty property) => + Func StreamNone = + (ODataPropertyStreamingContext context) => { return false; }; - foreach (Func ShouldStream in new Func[] { StreamCollection, StreamAll, StreamNone }) + foreach (Func ShouldStream in new Func[] { StreamCollection, StreamAll, StreamNone }) { foreach (Variant variant in GetVariants(ShouldStream)) { @@ -476,11 +476,11 @@ public void NotStreamingCollectionsWorks() ",\"name\":\"Thor\"" ); - Func ShouldStream = - (IEdmPrimitiveType type, bool isCollection, string propertyName, IEdmProperty property) => + Func ShouldStream = + (ODataPropertyStreamingContext context) => { numberOfTimesCalled++; - return !isCollection; + return !context.IsCollection; }; foreach (Variant variant in GetVariants(ShouldStream)) @@ -1155,10 +1155,10 @@ public void ReadStringPropertyAsStream() ",\"name\":\"Thor\"" ); - Func ShouldStream = - (IEdmPrimitiveType type, bool isCollection, string propertyName, IEdmProperty property) => + Func ShouldStream = + (ODataPropertyStreamingContext context) => { - return propertyName == "name"; + return context.PropertyName == "name"; }; foreach (Variant variant in GetVariants(ShouldStream)) @@ -1209,8 +1209,8 @@ public void CanReadStringAndBinaryPropertiesIndividually() ",\"age\":4000" ); - Func ShouldStream = - (IEdmPrimitiveType type, bool isCollection, string propertyName, IEdmProperty property) => + Func ShouldStream = + (ODataPropertyStreamingContext context) => { return true; }; @@ -1260,10 +1260,10 @@ public void ReadBinaryPropertyAsStream() ",\"binaryAsStream\":\"" + binaryValue + "\"" ); - Func ShouldStream = - (IEdmPrimitiveType type, bool isCollection, string propertyName, IEdmProperty property) => + Func ShouldStream = + (ODataPropertyStreamingContext context) => { - return type != null && type.IsBinary(); + return context.PrimitiveType != null && context.PrimitiveType.IsBinary(); }; foreach (Variant variant in GetVariants(ShouldStream)) @@ -1310,8 +1310,8 @@ public void CannotSkipStreamProperties() ",\"name\":\"Thor\"" ); - Func ShouldStream = - (IEdmPrimitiveType type, bool isCollection, string propertyName, IEdmProperty property) => + Func ShouldStream = + (ODataPropertyStreamingContext context) => { return true; }; @@ -1349,7 +1349,7 @@ private enum MetadataLevel Full } - private static IEnumerable GetVariants(Func readAsStream, bool includeUnordered = true) + private static IEnumerable GetVariants(Func readAsStream, bool includeUnordered = true) { List variants = new List(); foreach (ODataVersion version in new ODataVersion[] { ODataVersion.V4, ODataVersion.V401 }) @@ -1377,7 +1377,7 @@ private static IEnumerable GetVariants(Func - 2020 + 2026 $([System.Convert]::ToUInt16('$([MSBuild]::Add(1, $([MSBuild]::Subtract($([System.DateTime]::Now.Year), $(VersionStartYear)))))$([System.DateTime]::Now.ToString("MMdd"))')) $([System.Convert]::ToString($(VersionDateCode))) From 666b17ae0849d4a1e6d3d0c3090e3c19801229ff Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Mon, 19 Jan 2026 21:26:16 +0300 Subject: [PATCH 3/6] sealed ODataPropertyStreamingContext to fix failing test Microsoft.Test.Taupo.SealedPublicTypes --- azure-pipelines-nightly.yml | 2 +- azure-pipelines-rolling.yml | 2 +- src/Microsoft.OData.Core/ODataPropertyStreamingContext.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines-nightly.yml b/azure-pipelines-nightly.yml index 8f27ea25e6..7c7fe3456b 100644 --- a/azure-pipelines-nightly.yml +++ b/azure-pipelines-nightly.yml @@ -36,7 +36,7 @@ extends: parameters: pool: name: MSSecurity-1ES-Build-Agents-Pool - image: MSSecurity-1ES-Windows-2019 + image: MSSecurity-1ES-Windows-2022 os: windows customBuildTags: - ES365AIMigrationTooling diff --git a/azure-pipelines-rolling.yml b/azure-pipelines-rolling.yml index 37857776c5..dd3014c298 100644 --- a/azure-pipelines-rolling.yml +++ b/azure-pipelines-rolling.yml @@ -33,7 +33,7 @@ extends: parameters: pool: name: MSSecurity-1ES-Build-Agents-Pool - image: MSSecurity-1ES-Windows-2019 + image: MSSecurity-1ES-Windows-2022 os: windows customBuildTags: - ES365AIMigrationTooling diff --git a/src/Microsoft.OData.Core/ODataPropertyStreamingContext.cs b/src/Microsoft.OData.Core/ODataPropertyStreamingContext.cs index aa3fd3fa2d..12591d7ce2 100644 --- a/src/Microsoft.OData.Core/ODataPropertyStreamingContext.cs +++ b/src/Microsoft.OData.Core/ODataPropertyStreamingContext.cs @@ -12,7 +12,7 @@ namespace Microsoft.OData /// /// Context information for reading an OData property /// - public class ODataPropertyStreamingContext + public sealed class ODataPropertyStreamingContext { /// /// The primitive type of the property being read, or null if unknown. From 3e2505c2f279af7814f3512d488f189a6e5dd9d1 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Tue, 20 Jan 2026 08:31:31 +0300 Subject: [PATCH 4/6] Revert back to Windows 2019 --- azure-pipelines-nightly.yml | 2 +- azure-pipelines-rolling.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines-nightly.yml b/azure-pipelines-nightly.yml index 7c7fe3456b..8f27ea25e6 100644 --- a/azure-pipelines-nightly.yml +++ b/azure-pipelines-nightly.yml @@ -36,7 +36,7 @@ extends: parameters: pool: name: MSSecurity-1ES-Build-Agents-Pool - image: MSSecurity-1ES-Windows-2022 + image: MSSecurity-1ES-Windows-2019 os: windows customBuildTags: - ES365AIMigrationTooling diff --git a/azure-pipelines-rolling.yml b/azure-pipelines-rolling.yml index dd3014c298..37857776c5 100644 --- a/azure-pipelines-rolling.yml +++ b/azure-pipelines-rolling.yml @@ -33,7 +33,7 @@ extends: parameters: pool: name: MSSecurity-1ES-Build-Agents-Pool - image: MSSecurity-1ES-Windows-2022 + image: MSSecurity-1ES-Windows-2019 os: windows customBuildTags: - ES365AIMigrationTooling From 778083632979323c0091db5d896d72c3cc8dde04 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Tue, 20 Jan 2026 09:15:39 +0300 Subject: [PATCH 5/6] fix failing tests --- .../PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl | 2 +- .../BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl | 2 +- .../BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl index 6d6b17cb3e..c8cec86c03 100644 --- a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl +++ b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl @@ -5189,7 +5189,7 @@ public class Microsoft.OData.ODataPropertyInfo : Microsoft.OData.ODataItem { Microsoft.OData.Edm.EdmPrimitiveTypeKind PrimitiveTypeKind { public virtual get; public virtual set; } } -public class Microsoft.OData.ODataPropertyStreamingContext { +public sealed class Microsoft.OData.ODataPropertyStreamingContext { public ODataPropertyStreamingContext () System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.KeyValuePair`2[[System.String],[System.Object]]]] CustomPropertyAnnotations { public get; } diff --git a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl index dcb0c649ec..adacf07612 100644 --- a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl +++ b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl @@ -5189,7 +5189,7 @@ public class Microsoft.OData.ODataPropertyInfo : Microsoft.OData.ODataItem { Microsoft.OData.Edm.EdmPrimitiveTypeKind PrimitiveTypeKind { public virtual get; public virtual set; } } -public class Microsoft.OData.ODataPropertyStreamingContext { +public sealed class Microsoft.OData.ODataPropertyStreamingContext { public ODataPropertyStreamingContext () System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.KeyValuePair`2[[System.String],[System.Object]]]] CustomPropertyAnnotations { public get; } diff --git a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl index 6d6b17cb3e..c8cec86c03 100644 --- a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl +++ b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl @@ -5189,7 +5189,7 @@ public class Microsoft.OData.ODataPropertyInfo : Microsoft.OData.ODataItem { Microsoft.OData.Edm.EdmPrimitiveTypeKind PrimitiveTypeKind { public virtual get; public virtual set; } } -public class Microsoft.OData.ODataPropertyStreamingContext { +public sealed class Microsoft.OData.ODataPropertyStreamingContext { public ODataPropertyStreamingContext () System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.KeyValuePair`2[[System.String],[System.Object]]]] CustomPropertyAnnotations { public get; } From 37909b343eedf6e5afd5d16000f5bf60d2464227 Mon Sep 17 00:00:00 2001 From: Samuel Wanjohi Date: Tue, 20 Jan 2026 10:47:01 +0300 Subject: [PATCH 6/6] fix failing tests --- .../PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl | 2 +- .../BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl | 2 +- .../BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl index c8cec86c03..6d6b17cb3e 100644 --- a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl +++ b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.net45.bsl @@ -5189,7 +5189,7 @@ public class Microsoft.OData.ODataPropertyInfo : Microsoft.OData.ODataItem { Microsoft.OData.Edm.EdmPrimitiveTypeKind PrimitiveTypeKind { public virtual get; public virtual set; } } -public sealed class Microsoft.OData.ODataPropertyStreamingContext { +public class Microsoft.OData.ODataPropertyStreamingContext { public ODataPropertyStreamingContext () System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.KeyValuePair`2[[System.String],[System.Object]]]] CustomPropertyAnnotations { public get; } diff --git a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl index adacf07612..dcb0c649ec 100644 --- a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl +++ b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard1.1.bsl @@ -5189,7 +5189,7 @@ public class Microsoft.OData.ODataPropertyInfo : Microsoft.OData.ODataItem { Microsoft.OData.Edm.EdmPrimitiveTypeKind PrimitiveTypeKind { public virtual get; public virtual set; } } -public sealed class Microsoft.OData.ODataPropertyStreamingContext { +public class Microsoft.OData.ODataPropertyStreamingContext { public ODataPropertyStreamingContext () System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.KeyValuePair`2[[System.String],[System.Object]]]] CustomPropertyAnnotations { public get; } diff --git a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl index c8cec86c03..6d6b17cb3e 100644 --- a/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl +++ b/test/PublicApiTests/BaseLine/Microsoft.OData.PublicApi.netstandard2.0.bsl @@ -5189,7 +5189,7 @@ public class Microsoft.OData.ODataPropertyInfo : Microsoft.OData.ODataItem { Microsoft.OData.Edm.EdmPrimitiveTypeKind PrimitiveTypeKind { public virtual get; public virtual set; } } -public sealed class Microsoft.OData.ODataPropertyStreamingContext { +public class Microsoft.OData.ODataPropertyStreamingContext { public ODataPropertyStreamingContext () System.Collections.Generic.IEnumerable`1[[System.Collections.Generic.KeyValuePair`2[[System.String],[System.Object]]]] CustomPropertyAnnotations { public get; }