9

The situation

I'm trying to expand "Item" to three levels:

Item.Product.Model.Type

So I call this nested query options url:

http://xxx/api/Items?$expand=Product($expand=Model($expand=Type))

I Get a warning that the max depth of 2 has been reached so I set the suggested MaxExpansionDepth attribute to 3. But then, the "Type" property is not returned! This is covered by this SO question

Then I look at the official OData V4 standard and it says I should use slashes in the expand option, like so:

http://xxx/api/Items?$expand=Product/Model/Type

But that gives me an error telling:

The query specified in the URI is not valid. Found a path traversing multiple navigation properties. Please rephrase the query such that each expand path contains only type segments and navigation properties.

Which this SO answer covers but the answer is contradictory to the official OData doc. What does that even mean anyway.

The question

What is the official, standard and working way of using the $expand query option for deep levels with OData v4 and Web API 2.2

Community
  • 1
  • 1
Jerther
  • 5,558
  • 8
  • 40
  • 59

1 Answers1

12

After working with Jerther in chat, we narrowed the problem down to the expanded properties not being marked as contained navigations. As a result, the OData framework was removing them since they did not have corresponding entity sets defined. Updating the model to specifically declare the containment appears to have solved the problem.

Containment can be specified in a couple of ways, depending on what model builder is being used. In the case of the ODataConventionModelBuilder you can add the System.Web.OData.Builder.ContainedAttribute to the property in question, while for the ODataModelBuilder you can use the ContainsMany<T> method on the EntityTypeConfiguration instance for the containing class.

Also, at the moment, a cascaded expand will stop where a complex type contains an Entity type.

UPDATE:

Defining all types in the chain as EntitySet works.

builder.EntitySet<Item>("Items");
builder.EntitySet<Product>("Products");
builder.EntitySet<Model>("Models");
builder.EntitySet<Type>("Types");

It seems defining them as EntityType isn't sufficient.

see here: https://github.com/OData/WebApi/issues/226

Original Answer

I tried reproing your situation and couldn't. Is it possible that "Types" isn't being set in your action? Here was my little repro

public class ItemsController : ODataController
{
    [HttpGet]
    [EnableQuery(MaxExpansionDepth = 10)]
    [ODataRoute("/Items")]
    public IHttpActionResult GetItems()
    {
        return this.Ok(CreateItem());
    }

    private Item CreateItem()
    {
        return new Item
        {
            Id = 1,
            Products = new Product[]
            {
                new Product
                {
                    Id = 2,
                    Models = new Model[]
                    {
                        new Model
                        {
                            Id = 3,
                            Types = new MyType[]
                            {
                                new MyType
                                {
                                    Id = 4,
                                },
                            },
                        },
                    },
                },
            },
        };
    }
}

Which when called with /Items?$expand=Products($expand=Models($expand=Types)) resulted in the following:

{
    "@odata.context": "http://localhost:9001/$metadata#Items/$entity",
    "Id": 1,
    "Products@odata.context": "http://localhost:9001/$metadata#Items(1)/Products",
    "Products": [{
        "Id": 2,
        "Models@odata.context": "http://localhost:9001/$metadata#Items(1)/Products(2)/Models",
        "Models": [{
            "Id": 3,
            "Types@odata.context": "http://localhost:9001/$metadata#Items(1)/Products(2)/Models(3)/Types",
            "Types": [{
                "Id": 4
            }]
        }]
    }]
}
Brad
  • 4,089
  • 2
  • 16
  • 26
  • As I said in my question, the attribute is set to 3. I also tried 4, no change. – Jerther Feb 13 '15 at 13:13
  • Ah sorry! I blame my small device for my inability to read. I'll noodle on it and delete this answer if I've got nothing. – Brad Feb 13 '15 at 14:34
  • np. I've been doing research on this for many hours, please still let me know if you find anything of interest. – Jerther Feb 13 '15 at 14:47
  • I've rewritten the answer with what I tried, maybe it'll give you an idea. – Brad Feb 13 '15 at 16:07
  • I tried the same and the Types are not returned: http://pastebin.com/n2VtfDPm also, i'm using ODataController, and ODataConventionModelBuilder. I think we're on to something – Jerther Feb 13 '15 at 16:57
  • Here's my complete code - it doesn't sound that much different to what you have but maybe you'll notice something: http://1drv.ms/1E8JXCn – Brad Feb 13 '15 at 20:57
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/70878/discussion-between-jerther-and-brad). – Jerther Feb 13 '15 at 21:44
  • I believe there is no notification in the chat. I added some information there, and I won't mind continuing here afterwards. – Jerther Feb 18 '15 at 21:35
  • I've made some discoveries around complex and entity types. I'm updating the answer as I find more. – Jerther Feb 20 '15 at 20:59
  • This is still an issue in Microsoft.AspNet.OData v7.5.1, the solution is to either: Set the `[Contained]` attribute on each navigation property _FROM THE **RESPONSE** ROOT_ or to set the `EntityTypeConfiguration` correctly with `ContainsMany` or the single instance variants, but _ALL_ entities in the chain will need this set, not just the final 1 or two levels that you are having trouble expanding. – Chris Schaller Oct 29 '20 at 00:42