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

5

10

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.

Steelmaker answered 8/2, 2013 at 22:24 Comment(3)
Since Expand only seems to work on the first child can you invert your query? So for your SO example start with Comments, e.g. Comments.Expand(c => c.User).Expand(c => c.Post).Take(10)Cephalochordate
@Phil, Alas, no. This example is simple, but my actual query is very complex and has several "sub sections".Steelmaker
The link is dead.Illogical
H
7

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.

Heaveho answered 15/2, 2013 at 11:43 Comment(1)
Very, very Interesting.... So the server allows it, just not the client library. I think that answers my question.Steelmaker
C
2

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.

Collegiate answered 11/2, 2013 at 17:6 Comment(3)
I did not know you could do that. This solves half my issue. (I still have an anonymous type that I can't use AutoMapper on). If no one responds with a full answer, then I will select yours.Steelmaker
You could create a concrete type in place of the anonymous one. e.g. class MyProjection{ int Id{...}, ... }; select new MyProjection { Id = u.Id ... }Cephalochordate
As Phil says, the anonymous type can be replaced. I have extended the code snippet above to show how the nested anonymous type can be replaced.Collegiate
F
2

You can do,

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

That would result in the request,

https://data.stackexchange.com/stackoverflow/atom/Users()?$top=10&$expand=Comments&$select=Id,Reputation,Comments/Id

Frizzell answered 15/2, 2013 at 18:27 Comment(0)
L
1

You can select particular properties from subObjects.

e.g. in the example for StackOverflow, I can execute following query successfully in LINQPad.

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

In your case, you can write a query like this:

Orders.Select(z => new { StateName = z.SubOrder.Addresses.Select(a => a.PrimaryState),
                     z.SubOrder.Cost,        
                     z.SubOrder.SubOrderId,
                     z.Sequence});   
Lovable answered 11/2, 2013 at 19:15 Comment(1)
This is basically doing another projection, I end up with anonymous types.Steelmaker
N
0

The Expand is done on the query. See How to: Load Related Entities (WCF Data Services)

You want something like:

Orders
  .Expand("StateRef")
  .Select(z => new { ... } );
Nanete answered 8/2, 2013 at 22:39 Comment(10)
That gives this exception: NotSupportedException: Cannot create projection while there is an explicit expansion specified on the same query.Steelmaker
Please show the entire code for the Orders query. You can use commas in the .Expand argument for multiple expansions.Nanete
But you cannot have an expand clause and a select clause. On any query and any endpoint. I used Expand because that is the functionality I am looking for. But I have never been able to combine an Expand and a Select. If you can please post an example (against Netflix or StackOverFlows feeds).Steelmaker
Read the link in the example it has an example.Nanete
As I said previously, can you post the code that creates the orders query.Nanete
None of the queries in that link use projection. They just expand and bring down all columns. That example is contrived (based on my real query). I will try to get a real query against a public endpoint.Steelmaker
Just add ",StateRef" to the original expand.Nanete
Here is an example query. Users.Take(10).Select(x=>new { x.Id, x.Reputation, x.Comments}) Run it against "data.stackexchange.com/stackoverflow/atom" Note that the 'Comments' object has a 'Post' object on it. That returns null. I am wanting a way to expand it (without having to manually setup a projection for it)Steelmaker
You could do this Users.Expand("Comments/Post").Take(10) but that brings down EVERYTHING. That is why I am trying to use projection.Steelmaker
You say >>Just add ",StateRef" to the original expand<< But there is no Expand. I cannot see a way to have one with a Select statement.Steelmaker

© 2022 - 2024 — McMap. All rights reserved.