Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default Expanding of Navigation Properties in OData v9 for Complex Types #1328

Open
KavyaNelli opened this issue Oct 22, 2024 · 6 comments
Open
Assignees
Labels
bug Something isn't working follow-up

Comments

@KavyaNelli
Copy link

Assemblies affected
v9
Describe the bug
I’m working with OData v9 and am currently facing an issue when trying to return a complex type from an OData function that includes a navigation property.

Scenario: I have defined a complex type Address in my EDM, which includes a navigation property PersonDetails that links to the PersonDetails entity. The PersonDetails entity further includes a collection navigation property to Orders.
Reproduce steps
The simplest set of steps to reproduce the issue. If possible, reference a commit that demonstrates the issue.

Data Model
Model Definition:
builder.ComplexType<Address>();

EDM Metadata:

<Function Name="AddressLevel" IsBound="true">
            <Parameter Name="bindingParameter" Type="Eis.PersonDetails" />
            <ReturnType Type="Collection(Eis.PersonDetails)" />
</Function>
<ComplexType Name="Address">
            <Property Name="PinCode" Type="Edm.Int32" Nullable="false" />
            <NavigationProperty Name="PersonDetails" Type="Eis.PersonDetails" />
</ComplexType>
<EntityType Name="PersonDetails">
            <Key>
                <PropertyRef Name="PersonNumber" />
            </Key>
            <Property Name="PersonNumber" Type="Edm.String" Nullable="false" />
            <Property Name="FirstName" Type="Edm.String" Nullable="false" />
            <NavigationProperty Name="Orders" Type="Collection(Eis.Orders)" />
</EntityType>
<EntityType Name="Orders">
            <Key>
                <PropertyRef Name="OrderNumber" />
            </Key>
            <Property Name="OrderNumber" Type="Edm.String" Nullable="false" />
            <Property Name="Cost" Type="Edm.Int32" Nullable="false" />
</EntityType>

I am overriding the EnableQueryAttribute to attempt to customize the default behavior to expand PersonDetails and Orders.	

By default, only PinCode is fetched.

[
    {
        "PinCode": 32451
    },
    {
        "PinCode": 2351
    }
]

When I modify my EDM as below, entire personDetails is expanding(we want only few fields to expand) and yet Orders unexpanded.

Updated Model Definition:

builder.ComplexType<Address>().ContainsRequired(p => p.PersonDetails).AutoExpand = true;

Output:

[
{
    "PersonDetails": {
        "PersonNumber": "5255",
        //all other properties of personDetails
    },
    "PinCode": 52002
},
{
    "PersonDetails": {
        "PersonNumber": "5435",
        //all other properties of personDetails
    },
    "PinCode": 54213
}
]

Expected Output:

[
{
    "PersonDetails": {
		"Orders": [
            {
                "OrderNumber": "E528155",
                "Cost": 2345,
            }
        ],
        "PersonNumber": "5255",
        "FirstName": "Jack",
    },
    "PinCode": 52002
},
{
    "PersonDetails": {
		"Orders": [
            {
                "OrderNumber": "E528155",
                "Cost": 213
            }
        ],
        "PersonNumber": "5435",
        "FirstName": "Phill",
    },
    "PinCode": 54213
}
]

I have referred to below links for insights into this limitation, but I'm open to any other suggestions or workarounds that might help achieve the expected output.

[https://github.com/OData/WebApi/issues/2669]
[https://github.com//issues/1275]

@KavyaNelli KavyaNelli added the bug Something isn't working label Oct 22, 2024
@julealgon
Copy link
Contributor

I'm a bit confused by your "current" vs "expected". You want just some properties of PersonDetails to appear, even if they are not navigation properties?

Can you add a full example with all properties you are getting vs what you want?

Keep in mind "expand" is only about navigations. If you want other properties to not show that are not navigations you are now talking about "select", which is different.

@KavyaNelli
Copy link
Author

KavyaNelli commented Oct 22, 2024

You can ignore my commented line
Current:

[
{
    "PersonDetails": {
        "PersonNumber": "5255",
        "FirstName": "Jack"
    },
    "PinCode": 52002
},
{
    "PersonDetails": {
        "PersonNumber": "5435",
        "FirstName": "Phill"
    },
    "PinCode": 54213
}
]

Expected Output:

[
{
    "PersonDetails": {
		"Orders": [
            {
                "OrderNumber": "E528155",
                "Cost": 2345,
            }
        ],
        "PersonNumber": "5255",
        "FirstName": "Jack"
    },
    "PinCode": 52002
},
{
    "PersonDetails": {
		"Orders": [
            {
                "OrderNumber": "E528155",
                "Cost": 213
            }
        ],
        "PersonNumber": "5435",
        "FirstName": "Phill",
    },
    "PinCode": 54213
}
]

I want my orders entity in my personDetails to be expanded by default

@WanjohiSammy
Copy link
Contributor

WanjohiSammy commented Oct 22, 2024

@KavyaNelli
Try the following:

  1. Add this line of code:

    builder.EntityType<PersonDetails>().ContainsRequired(p => p.Orders).AutoExpand = true;
  2. Add [AutoExpand] attribute on top of property Orders autoexpand-attribute

  3. Use $expand query option

@KavyaNelli
Copy link
Author

Adding this line
builder.EntityType().ContainsRequired(p => p.Orders).AutoExpand = true;

is not expanding my orders(suspecting that my Address is a complex type)

And we also have many nested entites hence we donot want to go with this approach, because doing it at every level will be difficult.

Adding [AutoExpand] for Orders will expand all the properties, we have other endpoints where we dont want to expand all the properties.

@WanjohiSammy
Copy link
Contributor

@KavyaNelli With the below setup, I can return the expected result.
Could you kindly share sample code to help with the repro:

Controller

public class PersonDetailsController : ODataController
{
    public PersonDetailsController() { }

    private static IEnumerable<Address> _addresses = new List<Address>
    {
        new Address { PinCode = 123456, PersonDetails = new PersonDetails { PersonNumber = "123", FirstName = "John", Orders = new List<Order> { new Order { Id = 1, Amount = 100 } } } },
        new Address { PinCode = 456789, PersonDetails = new PersonDetails { PersonNumber = "456", FirstName = "Jane", Orders = new List<Order> { new Order { Id = 2, Amount = 200 }, new Order { Id = 3, Amount = 1000 } } } }
    };

    [HttpGet("odata/PersonDetails/AddressLevel")]
    public IActionResult AddressLevel()
    {
        return Ok(_addresses);
    }
}

EdmModel

public static IEdmModel GetEdmModel()
{
    var builder = new ODataConventionModelBuilder();
    builder.Namespace = "NS";
    builder.EntitySet<PersonDetails>("PersonDetails");

    var personDetailsType = builder.EntityType<PersonDetails>();
    personDetailsType
        .Function("AddressLevel")
        .ReturnsCollection<PersonDetails>();

    return builder.GetEdmModel();
}

Response: /PersonDetails/AddressLevel

[
    {
        "pinCode": 123456,
        "personDetails": {
            "personNumber": "123",
            "firstName": "John",
            "orders": [
                {
                    "id": 1,
                    "amount": 100
                }
            ]
        }
    },
    {
        "pinCode": 456789,
        "personDetails": {
            "personNumber": "456",
            "firstName": "Jane",
            "orders": [
                {
                    "id": 2,
                    "amount": 200
                },
                {
                    "id": 3,
                    "amount": 1000
                }
            ]
        }
    }
]

@julealgon
Copy link
Contributor

@WanjohiSammy that's not an OData payload.

Does it make a difference if you change the code like this:

-public IActionResult AddressLevel()
+public ActionResult<IEnumerable<Address>> AddressLevel()
{
-    return Ok(_addresses);
+    return _addresses;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working follow-up
Projects
None yet
Development

No branches or pull requests

3 participants