11

Problem

In an OData 4 service on Web API, what is the proper way to call nested $expand from a .NET client? We are using the OData Client Generator. Back in the prior WCF Data Services with OData 3 service we could call .Expand("Customers/Orders"). In Web API with OData 4 we can no longer do so and receive the following should you attempt .Expand("Customers/Orders"):

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.

Workaround

We are able to work around this by calling expand like so: .Expand("Customers($expand=Orders)"). In non-nested $expand scenarios, I like the lambda support like so .Expand(d => d.Customers). Is there a proper way in .NET OData 4 client to call nested expands without the unfortunate magic string of .Expand("Customers($expand=Orders)")? If not, is there a cleaner string pattern like "Customers/Orders" that would work? Thanks.

Adam Caviness
  • 3,424
  • 33
  • 37
  • In the OData v3 scenario, you can see this [MS recommendation](http://social.msdn.microsoft.com/Forums/en-US/fc811ad3-a1e2-46d7-b263-abceecb67c93/exapnding-with-loadproperty-with-nested-levels?forum=adodotnetdataservices). – Adam Caviness Aug 12 '14 at 15:41
  • Thanks for the workaround for the .NET OData 4 client: .Expand("Customers($expand=Orders)") In an url you can use it like this: ?$expand=Customers($expand=Orders) Reference: https://stackoverflow.com/a/28492946/2908623 – Jesper1 Mar 03 '20 at 09:47

3 Answers3

3

The request that you want to send is:

GET http://host/service/Customers/Orders

right?

According to the OData protocol:

To request related entities according to a particular relationship, the client issues a GET request to the source entity’s request URL, followed by a forward slash and the name of the navigation property representing the relationship.

So such request is not supported as the "Customers" before "/Orders" is the name of an entity set instead of a single entity. You can only write nested expanding like:

GET http://host/service/Customers(1)/Orders

Which corresponds to the following code snippets using OData V4 Code Generator:

var orders = context.Customers.ByKey(new Dictionary<string, object>() { { "ID", 1 } }).Orders.Execute();

And you need to iterate through all the customers to get all their orders.

Yi Ding - MSFT
  • 2,864
  • 16
  • 16
  • 1
    @Yi_Ding, It's awesome to get a response from you. However, may you please re-read my question? It appears you've mistaken the topic. When I say expand, I mean expand as in $expand. – Adam Caviness Aug 12 '14 at 15:28
3

A little extension for that :

        public static DataServiceQuery<TSource> Expand<TSource,TNavigation,TExpand>(this DataServiceQuery<TSource> dataServiceQuery, Expression<Func<TSource, DataServiceCollection<TNavigation>>> expression,  Expression<Func<TNavigation,TExpand>> navigation)
    {
        var expressionName = (expression.Body as System.Linq.Expressions.MemberExpression).Member.Name;
        var navigationName = (navigation.Body as System.Linq.Expressions.MemberExpression).Member.Name;


        return dataServiceQuery.Expand($"{expressionName}($expand={navigationName})");
    }

Now you have intellisense and type checking

Example :

db.Container.Expand(c=> c.Customers, customer => customer.Orders)

Dadv
  • 324
  • 2
  • 17
  • This is a great answer, but I'm scratching my head over how you actually call this function.... – DarinH May 06 '21 at 16:56
  • Figured it out, this specific function is for dealing with properties on an entity that return a +Collection+ of entities. So to use it, you might do this r = container.MyProducts.Expand(p => p.Categories, category => category.CategoryType) – DarinH May 06 '21 at 19:03
  • Yes, it's for nested object (or collection) in collection property. You could also use it like : r = container.MyProducts.Expand(p => p.Customers, customer=> customer.Orders) (like the initial question) – Dadv May 12 '21 at 15:00
1

In OData v4, it is not valid to expand multi levels, such as what you mentioned in the question: .Expand("Customers/Orders"). I dont think the client will support such API. Here is what in the ABNF http://docs.oasis-open.org/odata/odata/v4.0/os/abnf/odata-abnf-construction-rules.txt:

expand            = '$expand' EQ expandItem *( COMMA expandItem )
expandItem        = STAR [ ref / OPEN levels CLOSE ]
                  / expandPath
                    [ ref   [ OPEN expandRefOption   *( SEMI expandRefOption   ) CLOSE ]
                    / count [ OPEN expandCountOption *( SEMI expandCountOption ) CLOSE ]
                    /         OPEN expandOption      *( SEMI expandOption      ) CLOSE 
                    ]
expandPath        = [ qualifiedEntityTypeName "/" ] 
                    *( ( complexProperty / complexColProperty ) "/" [ qualifiedComplexTypeName "/" ] )
                    navigationProperty 
                    [ "/" qualifiedEntityTypeName ]
Tan Jinfu
  • 3,327
  • 1
  • 19
  • 20
  • 2
    @Tan_Jinfu Thanks, this is good information. But it begs the question, why then does '$expand=Customers($expand=Orders)' work in the MS implementation? – Adam Caviness Aug 12 '14 at 15:32
  • Also, there is a [MaxExpansionDepth property](http://msdn.microsoft.com/en-us/library/system.web.odata.enablequeryattribute.maxexpansiondepth(v=vs.118).aspx) on System.Web.OData.EnableQueryAttribute that states _Gets or sets the max expansion depth for the $expand query option. To disable the maximum expansion depth check, set this property to 0._ – Adam Caviness Aug 12 '14 at 18:56
  • 2
    Because the $expand=Orders in $expand=Customers($expand=Orders) is called a query option, and a query option can be $filter or another $expand, and the ABNF is:expandOption = expandRefOption / select / expand / levels – Tan Jinfu Aug 13 '14 at 00:11
  • 1
    How come it is not valid while it is clearly documented here: http://docs.oasis-open.org/odata/odata/v4.0/errata02/os/complete/part2-url-conventions/odata-v4.0-errata02-os-part2-url-conventions-complete.html#_Toc406398162 – Jerther Feb 12 '15 at 21:56
  • Nested $expand and $select is supported in the MS implementation of OData v4 on the server side, but the issue here is how to construct such a query using the proxy generated by the OData Client Generator... The key is to use the Linq select function to select the specific fields we want into an anonymous type. – Chris Schaller Aug 10 '16 at 15:11
  • I have Downvote because this answers suggest that it is not possible to achieve what the OP ask.., but I was facing the exact same problem, and the work-around suggested by the OP works very well. – Simon Jun 26 '21 at 12:33