Skip to content

Conversation

@gathogojr
Copy link
Contributor

@gathogojr gathogojr commented Feb 21, 2025

Fixes #1412

Fix regression causing navigation properties to be auto-expanded in typeless scenarios

The challenge we face is that EdmEntityObject, EdmComplexObject, EdmDeltaResourceObject, EdmDeltaComplexObject, and EdmDeltaDeletedResourceObject (together with their typed internal equivalents TypedEdmEntityObject and TypedEdmComplexObject) all implemement IDelta, IDeltaSetItem, and IEdmChangedObject interfaces directly or indirectly.

Illustration:

using Microsoft.AspNetCore.OData.Deltas;
using Microsoft.AspNetCore.OData.Formatter.Value;
using Microsoft.OData.Edm;

var orderEntityType = new EdmEntityType("NS", "Order");
var addressComplexType = new EdmComplexType("NS", "Address");

var orderEntityObject = new EdmEntityObject(orderEntityType);
var addressComplexObject = new EdmComplexObject(addressComplexType);
var orderDeltaResourceObject = new EdmDeltaResourceObject(orderEntityType);
var addressDeltaComplexObject = new EdmDeltaComplexObject(addressComplexType);
var orderDeltaDeletedResourceObject = new EdmDeltaDeletedResourceObject(orderEntityType);

Console.WriteLine($"EdmEntityObject - Is IDelta: {orderEntityObject is IDelta}, Is DeltaSetItem: {orderEntityObject is IDeltaSetItem}, Is IEdmChangedObject: {orderEntityObject is IEdmChangedObject}, Is DeltaResource: {orderEntityObject.IsDeltaResource()}");
Console.WriteLine($"EdmComplexObject - Is IDelta: {addressComplexObject is IDelta}, Is DeltaSetItem: {addressComplexObject is IDeltaSetItem}, Is IEdmChangedObject: {addressComplexObject is IEdmChangedObject}, Is DeltaResource: {addressComplexObject.IsDeltaResource()}");
Console.WriteLine($"EdmDeltaResourceObject - Is IDelta: {orderDeltaResourceObject is IDelta}, Is DeltaSetItem: {orderDeltaResourceObject is IDeltaSetItem}, Is IEdmChangedObject: {orderDeltaResourceObject is IEdmChangedObject}, Is DeltaResource: {orderDeltaResourceObject.IsDeltaResource()}");
Console.WriteLine($"EdmDeltaComplexObject - Is IDelta: {addressDeltaComplexObject is IDelta}, Is DeltaSetItem: {addressDeltaComplexObject is IDeltaSetItem}, Is IEdmChangedObject: {addressDeltaComplexObject is IEdmChangedObject}, Is DeltaResource: {addressDeltaComplexObject.IsDeltaResource()}");
Console.WriteLine($"EdmDeltaDeletedResourceObject - Is IDelta: {orderDeltaDeletedResourceObject is IDelta}, Is DeltaSetItem: {orderDeltaDeletedResourceObject is IDeltaSetItem}, Is IEdmChangedObject: {orderDeltaDeletedResourceObject is IEdmChangedObject}, Is DeltaResource: {orderDeltaDeletedResourceObject.IsDeltaResource()}");

Result:

EdmEntityObject - Is IDelta: True, Is DeltaSetItem: True, Is IEdmChangedObject: True, Is DeltaResource: True
EdmComplexObject - Is IDelta: True, Is DeltaSetItem: True, Is IEdmChangedObject: True, Is DeltaResource: True
EdmDeltaResourceObject - Is IDelta: True, Is DeltaSetItem: True, Is IEdmChangedObject: True, Is DeltaResource: True
EdmDeltaComplexObject - Is IDelta: True, Is DeltaSetItem: True, Is IEdmChangedObject: True, Is DeltaResource: True
EdmDeltaDeletedResourceObject - Is IDelta: True, Is DeltaSetItem: True, Is IEdmChangedObject: True, Is DeltaResource: True

For the above reason, you can't tell by interrogating the object itself where it's a delta resource - specifically for EdmEntityObject and EdmComplexObject that can be present in both delta and non-delta payloads.

What this basically means, is that if you have a controller action like the typeless scenario below, where Customer is a navigation property, the navigation property is auto-expanded whether or not there's an $expand on the URL:

public EdmEntityObjectCollection Get()
{
    var customerObject = new EdmEntityObject(TypelessEdmModel.CustomerEntityType);
    customerObject.TrySetPropertyValue("Id", 1);
    customerObject.TrySetPropertyValue("Name", "Sue");

    var orderObject = new EdmEntityObject(TypelessEdmModel.OrderEntityType);
    orderObject.TrySetPropertyValue("Id", 1);
    orderObject.TrySetPropertyValue("Amount", 310m);
    orderObject.TrySetPropertyValue("Customer", customerObject);

    return new EdmEntityObjectCollection(
        new EdmCollectionTypeReference(
            new EdmCollectionType(
                new EdmEntityTypeReference(TypelessEdmModel.OrderEntityType, false))))
    {
        orderObject
    };
}

The buggy logic is here:

bool isDelta = graph is IDelta || graph is IEdmChangedObject;

We use the ODataResourceSerializer to serialize both delta and non-delta payloads. When the above statement is evaluated, it'll always return true for nearly all (if not all) typeless Edm objects, including those in the code sample above, that shouldn't be serialized as a delta.

Telling whether an object is a delta or non-delta resource in typeless scenarios becomes a dicey affair.
I propose in this pull request to use the serializer context (via ODataSerializerContext) to indicate whether its a delta payload being written or not.

This change fixes the regression and addresses the challenge described above.

@gathogojr gathogojr force-pushed the fix/1412-fix-nav-props-auto-expanded-regression branch from e7c76b8 to 56b8452 Compare February 24, 2025 05:23
}
else
{
writeContext.IsDelta = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we using these method calls to determine when the writeContext.isDelta is set? What if someone overrides these methods? They'll have to set this property explicitly? What happens if they don't?

Copy link
Contributor

@anasik anasik Feb 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ElizabethOkerio , in #1413, I solved the same bug with a simple type comparison that only modifies one function in one file.

It's practically a one-liner and I would appreciate it if someone could review it. I think it's the most viable fix for this problem. I also added some tests very specific to the reported use-case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ElizabethOkerio Following our offline discussion, I have addressed the issue by setting the property from the output formatter where the serializer context is initialized

Copy link
Contributor

@anasik anasik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TypelessDataModel you created here under seems very similar to the one under Untyped. Are you sure your new tests wouldn't be well suited to that folder?

In fact, your entire Typeless folder seems very similar to the Untyped folder.

Meanwhile, the existing Typeless folder looks very different from this so adding these tests to that folder would be a drastic change.

@gathogojr gathogojr force-pushed the fix/1412-fix-nav-props-auto-expanded-regression branch from 1f6386e to 7676eb1 Compare February 28, 2025 08:25
@gathogojr
Copy link
Contributor Author

The TypelessDataModel you created here under seems very similar to the one under Untyped. Are you sure your new tests wouldn't be well suited to that folder?

In fact, your entire Typeless folder seems very similar to the Untyped folder.

Meanwhile, the existing Typeless folder looks very different from this so adding these tests to that folder would be a drastic change.

@anasik I went through the Untyped folder and I don't see a significant overlap. As a matter of fact, the Edm model in the Untyped folder is typed. In addition, the Typeless folder already had existing tests for typeless scenario

// See License.txt in the project root for license information.
// </copyright>
//------------------------------------------------------------------------------

Copy link
Contributor

@ElizabethOkerio ElizabethOkerio Mar 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a typeless data model. I don't know can we use a name for these tests and this folder that depicts what is being tested. Exa. DeltasWithTyplessModels or something like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ElizabethOkerio Do note the comment I added before the first model:

// Types included for comparison with typeless scenario

The main focus of the tests is typeless scenario but I added the models to verify that the serialized result is the same

ElizabethOkerio
ElizabethOkerio previously approved these changes Mar 3, 2025
@WanjohiSammy
Copy link
Member

LGTM

WanjohiSammy
WanjohiSammy previously approved these changes Mar 4, 2025
@gathogojr gathogojr dismissed stale reviews from WanjohiSammy and ElizabethOkerio via 0e2280d March 12, 2025 05:44
@gathogojr gathogojr force-pushed the fix/1412-fix-nav-props-auto-expanded-regression branch from 7676eb1 to 0e2280d Compare March 12, 2025 05:44
@gathogojr gathogojr merged commit 18a25ab into OData:main Mar 12, 2025
2 checks passed
@gathogojr gathogojr deleted the fix/1412-fix-nav-props-auto-expanded-regression branch March 12, 2025 10:04
ArnaudB88 added a commit to ArnaudB88/OData2Linq that referenced this pull request Jul 16, 2025
* Migrate to ESRP v5 (OData#1421)

* Migrate to ESRP v5

* Replace raw resource identifiers with variables

* Fix typo in KV variable

* Add Obsolete attribute to EdmDeltaResourceObject and
EdmDeltaComplexObject type

* Fix regression causing navigation properties to be auto-expanded in typeless scenarios (OData#1424)

* Bump version to 9.2.1 (OData#1437)

* Fix an issue where multiple flags are set and ensure correct deserialization (OData#1442)

* Fixes OData#1455 Add ISearchQueryValidator (OData#1456)

* Restructure AggregationBinder and ComputeBinder for extensibility (port OData#1378) (OData#1457)

* Bump version to 9.3.0 (OData#1464)

* Fix the typo of generaticType

* Fixes OData#580 Change PageResult<T> property names on serialization

* Fixes OData#1472: Custom ISearchBinder does not support deeply nested $expand (OData#1474)

* Fixes OData#1472: Custom ISearchBinder does not support deeply nested $expand

* Address the comments.

* Ensuring Url safe string key values. Aligning with ODL Client. Fixes OData#1390. (OData#1396)

* CA2254 fixes possible formatting errors

* bump to release version 9.3.1

* Enable minimal API OData (OData#1469)

* Enable minimal API OData

* Simple exclude the metadata and servicedocument reault out from the filter.

* add content-type into response header

* Enable Delta<T> as parameter

* update the comments and public api

* Bump to version 9.4.0 preview

* Fixes OData#1483: Regression with computed in $orderby with 'Could not find a property named xxx on ....' (OData#1486)

* Fixes OData#1483: Regression with computed in $orderby with 'Could not find a property named xxx on ....'

* Bump version to 9.3.2

* Fixes OData#1487 : Minimal API TimeZoneInfo for Serialization (OData#1488)

* Fixes OData#1487 : Minimal API TimeZoneInfo for Serialization
minimalApi

Enable ODataActionParameter and ODataUntypedActionParameter binding

* Address the comment to move the error messge to Resources

* Resolve the issue with IAsyncEnumerable (OData#1467)

* Resolve the issue with IAsyncEnumerable

* Resolve FormatException by escaping curly brackets and add tests for SRResources (OData#1475)

* Fix vulnerable dependencies in newtonsoft.json (OData#1489)

* Fix vulnerable dependencies
* Replace Microsoft.CodeAnalysis.FxCopAnalyzers with Microsoft.CodeAnalysis.NetAnalyzers

* Fixes OData#1494: Enable DeltaSet<T> for minimal API parameter binding
ArnaudB88 added a commit to ArnaudB88/OData2Linq that referenced this pull request Jul 16, 2025
* bump version

* Update reference to ASP.NET Core OData v9.3.2 (#7)

* Migrate to ESRP v5 (OData#1421)

* Migrate to ESRP v5

* Replace raw resource identifiers with variables

* Fix typo in KV variable

* Add Obsolete attribute to EdmDeltaResourceObject and
EdmDeltaComplexObject type

* Fix regression causing navigation properties to be auto-expanded in typeless scenarios (OData#1424)

* Bump version to 9.2.1 (OData#1437)

* Fix an issue where multiple flags are set and ensure correct deserialization (OData#1442)

* Fixes OData#1455 Add ISearchQueryValidator (OData#1456)

* Restructure AggregationBinder and ComputeBinder for extensibility (port OData#1378) (OData#1457)

* Bump version to 9.3.0 (OData#1464)

* Fix the typo of generaticType

* Fixes OData#580 Change PageResult<T> property names on serialization

* Fixes OData#1472: Custom ISearchBinder does not support deeply nested $expand (OData#1474)

* Fixes OData#1472: Custom ISearchBinder does not support deeply nested $expand

* Address the comments.

* Ensuring Url safe string key values. Aligning with ODL Client. Fixes OData#1390. (OData#1396)

* CA2254 fixes possible formatting errors

* bump to release version 9.3.1

* Enable minimal API OData (OData#1469)

* Enable minimal API OData

* Simple exclude the metadata and servicedocument reault out from the filter.

* add content-type into response header

* Enable Delta<T> as parameter

* update the comments and public api

* Bump to version 9.4.0 preview

* Fixes OData#1483: Regression with computed in $orderby with 'Could not find a property named xxx on ....' (OData#1486)

* Fixes OData#1483: Regression with computed in $orderby with 'Could not find a property named xxx on ....'

* Bump version to 9.3.2

* Fixes OData#1487 : Minimal API TimeZoneInfo for Serialization (OData#1488)

* Fixes OData#1487 : Minimal API TimeZoneInfo for Serialization
minimalApi

Enable ODataActionParameter and ODataUntypedActionParameter binding

* Address the comment to move the error messge to Resources

* Resolve the issue with IAsyncEnumerable (OData#1467)

* Resolve the issue with IAsyncEnumerable

* Resolve FormatException by escaping curly brackets and add tests for SRResources (OData#1475)

* Fix vulnerable dependencies in newtonsoft.json (OData#1489)

* Fix vulnerable dependencies
* Replace Microsoft.CodeAnalysis.FxCopAnalyzers with Microsoft.CodeAnalysis.NetAnalyzers

* Fixes OData#1494: Enable DeltaSet<T> for minimal API parameter binding
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

$select query option inside $expand is ignored

5 participants