Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expand in a Projection (Select) for WCF Data Services (OData)

Say I have an OData query that looks like this (My actual query is much more complex):

Orders.Select(z => new { z.SubOrder.Addresses,
                         z.SubOrder.Cost,
                         z.SubOrder.SubOrderId, 
                         z.Sequence});

This works fine. Except that the Address object has a sub object on it (StateRef). Since StateRef does a look-up on the State table, it is returned as null.

To illustrate, here is an example of how the address object Address might look:

Address:
    string         Street 1
    string         Street 2
    StateRef       PrimaryState
    string         City
    // ... 42 other string attributes not shown ...

The StateRef object has the name of the state on it, but also has some other important State properties (maybe state bird?)

So, what I am wondering is, do I have to now create a "sub projection" for z.SubOrder.Addresses that contains all 46 attributes just so that I can access the PrimaryState item? (I Hope NOT)

Aside from being way more coding, it also means I have to use anonymous types. Which makes my mapping have to be by hand (instead of using AutoMapper).

So what I am looking for is a way to "Expand" the StateRef inside the projection?

Something like this:

Orders.Select(z => new { z.SubOrder.Addresses.Expand("PrimaryState"),
                         z.SubOrder.Cost,        ^
                         z.SubOrder.SubOrderId,  |
                         z.Sequence});           |
                                                 |
// This is not allowed by the compiler ----------+

Trying this give this error:

Invalid anonymous type member declarator. Anonymous type members must be declared with a member assignment, simple name or member access.

Update: Here is an example query to illustrate what I am asking about:

Users.Take(10).Select(x=>new { x.Id, x.Reputation, x.Comments})

Run that against "data.stackexchange.com/stackoverflow/atom". You will see that Comments has a Post object that returns null.

I need that to return the values inside of it.

Note: I know I can manually type all of them out into a "sub" projection. Read above for why I do not want that.

like image 743
Vaccano Avatar asked Feb 08 '13 22:02

Vaccano


People also ask

What is expand in OData?

OData expand functionality can be used to query related data. For example, to get the Course data for each Enrollment entity, include ?$ expand=course at the end of the request path: This tutorial uses Postman to test the web API.

What is $select in OData?

The $select option specifies a subset of properties to include in the response body. For example, to get only the name and price of each product, use the following query: Console Copy. GET http://localhost/odata/Products?$select=Price,Name.

What is $value in OData?

The $value option is used to get individual properties of an Entity. There are two ways to get individual properties from an entity. We can get the response in either OData format or get the raw value of the property. We need to add method to the controller named GetProperty here property is a name of the property.


2 Answers

You don't have to create a sub-projection which lists out all 46 attributes, e.g.

(from u in Users
 select new 
    {
    u.Id, u.Reputation,Comments = ( from c in u.Comments 
                                    select new YourClass {comment = c, 
                                                          post= c.Post})
    }
)
.Take(10)


.....


public Class YourClass
    {
    public Comment comment {get;  set;}
    public Post post {get;set;}
    }

Not exactly the object graph I imagine you're after.

This aside, one can spend a lot of time trying to write a LinQ expression that will generate the correct OData query, we've found that it is much more time effecient to create your own OData query class with Expand, Filter, Select properties etc. i.e. go straight to writing OData queries instead of trying to craft LinQ queries.

like image 35
A Aiston Avatar answered Oct 21 '22 17:10

A Aiston


It is certainly possible to do that. For a proof of concept try executing this:

var uri = new Uri( "http://data.stackexchange.com/stackoverflow/atom/Users()?$top=10&$expand=Comments/Post&$select=Id,Reputation,Comments/" );
entities.Execute<User>( uri, "GET", false ).Select( x => new { x.Id, x.Reputation, x.Comments } );

The correct usage of expand is like this:

entities.Users.Expand( "Comments/Post" ).Take( 10 ).ToArray();

I don't know why the writers of the library have decided to disallow using expand with projections, but as the above proof of concept shows, it is certainly possible to do so.

If you don't mind receiving the entire user and making the projection after that, you can go with the second example. Else you can write your own helpers which will produce the URI from the first example, execute them, and add the projection after that.

like image 180
Ivan Zlatanov Avatar answered Oct 21 '22 16:10

Ivan Zlatanov