OData / odata.net

ODataLib: Open Data Protocol - .NET Libraries and Frameworks
https://docs.microsoft.com/odata
Other
688 stars 349 forks source link

Can't Read/Write enum as individual property/collection value #1733

Open mikepizzo opened 4 years ago

mikepizzo commented 4 years ago

For streaming support, we added the ability to write individual properties of a resource, as well as individual primitive values within a collection, by calling WritePrimitive. Similarly, we added a ReadState .Primitive for reading primitive values. Unfortunately, enum values are not considered primitive values, so we need to add a way to read/write individual enum properties and values within a collection.

Assemblies affected

OData .Net lib 7.6.3

Reproduce steps

You can write an individual string property as follows:

odataWriter.WriteStart(new ODataResource());
odataWriter.WriteStart(
    new ODataPropertyInfo
    {
          Name = "Name",
          PrimitiveTypeKind = PrimitiveTypeKind.String
    });
odataWriter.WritePrimitive(new ODataPrimitiveValue("string"));
odataWriter.WriteEnd(); // property
odataWriter.WriteEnd(); // resource

Similar patterns exist for writing a primitive value within a collection and for reading primitive properties/primitive values within a collection.

Expected result

There should be similar patterns for writing/reading an enum property, or value within a collection

Actual result

There is no equivalent for reading/writing an enum property, or value within a collection.

gathogojr commented 3 months ago

@mikepizzo @paulodero @WanjohiSammy The WritePrimitiveAsync method is a convenience method that one can use to write a single-valued primitive properties. To write collection-valued primitive properties as well as single-valued and collection-valued enum properties, one can use the WriteStart/WriteStartAsync(ODataPropertyInfo) method. The method can take an ODataProperty object and will handle the writing correctly. Here's how we can write a single-valued and a collection-valued enum property:

public async Task WriteEnumAsync()
{
    var tempServiceUri = "http://tempuri.org";
    var tempModel = new EdmModel();

    var tempEnumType = new EdmEnumType("NS", "Color");
    tempEnumType.AddMember("Black", new EdmEnumMemberValue(0));
    tempEnumType.AddMember("White", new EdmEnumMemberValue(1));
    tempModel.AddElement(tempEnumType);

    var tempEntityType = tempModel.AddEntityType("NS", "Entity");
    tempEntityType.AddKeys(tempEntityType.AddStructuralProperty("Id", EdmCoreModel.Instance.GetInt32(false)));
    tempEntityType.AddStructuralProperty("EnumProp",
        new EdmEnumTypeReference(tempEnumType, false));
    tempEntityType.AddStructuralProperty("EnumCollectionProp",
        new EdmCollectionTypeReference(new EdmCollectionType(new EdmEnumTypeReference(tempEnumType, false))));

    var tempEntityContainer = tempModel.AddEntityContainer("Default", "Container");
    var tempEntitySet = tempEntityContainer.AddEntitySet("Entities", tempEntityType);

    var tempResource = new ODataResource
    {
        TypeName = "NS.Entity"
    };

    var tempMessageWriterSettings = new ODataMessageWriterSettings
    {
        Version = ODataVersion.V4,
        EnableMessageStreamDisposal = false,
        BaseUri = new Uri(tempServiceUri),
        ODataUri = new ODataUri { ServiceRoot = new Uri(tempServiceUri) }
    };

    await using (var tempStream = new MemoryStream())
    {
        IODataResponseMessage tempResponseMessage = new InMemoryMessage { Stream = tempStream };

        await using (var tempMessageWriter = new ODataMessageWriter(tempResponseMessage, tempMessageWriterSettings))
        {
            var tempResourceWriter = await tempMessageWriter.CreateODataResourceWriterAsync(tempEntitySet, tempEntityType);

            await tempResourceWriter.WriteStartAsync(tempResource);
            await tempResourceWriter.WriteStartAsync(
                new ODataPropertyInfo { Name = "Id" });
            await tempResourceWriter.WritePrimitiveAsync(new ODataPrimitiveValue(1));
            await tempResourceWriter.WriteEndAsync();
            await tempResourceWriter.WriteStartAsync(
                new ODataProperty { Name = "EnumProp", Value = new ODataEnumValue("Black ") });
            await tempResourceWriter.WriteEndAsync();
            await tempResourceWriter.WriteStartAsync(
                new ODataProperty { Name = "EnumCollectionProp", Value = new ODataCollectionValue { Items = [new ODataEnumValue("White "), new ODataEnumValue("Black ")] } });
            await tempResourceWriter.WriteEndAsync();
            await tempResourceWriter.WriteEndAsync();
        }

        tempStream.Position = 0;
        var tempPayload = await new StreamReader(tempStream).ReadToEndAsync();
    }
}

We don't have a feature gap here. A method that accepts an ODataEnumValue and another method that accepts an ODataCollectionValue (to support writing primitive and enum collection-valued properties) would be an enhancement. This should be a P3 IMO.

mikepizzo commented 3 months ago

You are right that the repro was a simplified example that could be written as a property. However, it was intended to be a simple example.

The streaming functionality was added in order to support scenarios where data was read from one source and written to the wire without holding data in memory. In particular, writing large objects with large collections.

In particular, I can start writing a collection and then write values within that collection. If the collection contains enums (either it is typed as a collection of enums, a collection of primitive types, or an untyped collection) then I cannot write it in a streamed fashion -- I have to construct the ODataCollectionValue, as per your example.

So the functionality gap is not being able to stream collections that may contain enum values (and, for streaming primitive/untyped collections I may not know before I start writing if it contains an enum)

It is also unfortunate to have to handle enums differently than other types, but that's not the feature gap -- the feature gap is being able to stream collections that may contain enums.