OData Routing

Topics: ASP.NET Web API
Feb 27, 2013 at 5:32 PM
Following Mike Wasson's post on basic navigation properties I was able to get direct one to one entity relationships working. (Thanks Mike) But I'm having some difficulty with more complex navigation.

My case can be thought of as a basic 3 level one-to-many relationship, for example State -> County -> City. Using Mike's post I was able to get the route /States(7)/Governor working. What I'd like to do now is add a route like /States(7)/Cities which would return all the Cities within ever County within State 7. So I added a new method to the StatesController which has the following signature: public IQueryable<City> GetCities( [FromODataUri] int key ). When I attempt to GET from /States(7)/Cities I get an odata.error "This service doesn't support OData requests in the form '~/entityset/key/unresolved'."

Behind the scenes of all this I'm using EntityFramework code first. The POCO class for State has virtual properties for Governor and Counties but not Cities. The class for Counties has virtual properties for State and Cities.

What am I missing here? I haven't modified anything in my odata route, but I suspect I'll have to add something using HasNavigationPropertiesLink. However, I haven't been able to locate any resources that use this function without also using ODataRouteNames. I should also mention StatesController inherits from EntitySetController<State, int>. Though I don't know if that makes any difference.

Thanks in advance for any help you can offer.
Feb 27, 2013 at 6:30 PM
States(7)/Cities is not a valid odata url as, I assume, Cities is not a navigation property on State. You might want to add a property Cities in your State class and ignore it in your EntityFramework mapping layer, something like
public class State
{
    public IEnumerable<City> Cities
    {
        get
        {
            return Counties.SelectMany(c => c.Cities);
        }
    }

    public IEnumerable<County> Counties { get; set; }
}


public class StatesContext : DbContext
{
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<State>().Ignore(s => s.Cities);
    }
}
Mar 1, 2013 at 1:27 PM
Sorry for not getting back to you sooner. It would seem I've been living out of a meeting room this week. I just wanted to let you know that your solution does work, but I'm still wondering what is HasNavigationPropertiesLink for? I noticed it when I was using the ODataModelBuilder to build the model for my ODataRoute. The function exists in EntitySetConfiguration. If you or anyone has an example of it's use or can explain why someone might use it I would appreciate the knowledge. It's hard to make good use of a tool kit if you don't know what all the tools are.
Mar 14, 2013 at 4:33 AM
Exact same issue here, right down to returning IQueryable<T> and the SelectMany LINQ expression. I can get "one to one" relationships working fine, but not "one to many".

Ever figure this out?
Mar 14, 2013 at 7:02 AM
zlangner wrote:
Sorry for not getting back to you sooner. It would seem I've been living out of a meeting room this week. I just wanted to let you know that your solution does work, but I'm still wondering what is HasNavigationPropertiesLink for? I noticed it when I was using the ODataModelBuilder to build the model for my ODataRoute. The function exists in EntitySetConfiguration. If you or anyone has an example of it's use or can explain why someone might use it I would appreciate the knowledge. It's hard to make good use of a tool kit if you don't know what all the tools are.
HasNavigagationPropertiesLink is used to configure the navigation links for an entity set. If you are using the ODataConventionModelBuilder, the model builder already takes care of setting up the navigation links following the OData link conventions. If for some reason (like lets says containment) you want to deviate from OData URL conventions, you can use this method.
Mar 14, 2013 at 7:02 AM
ShadowChaser wrote:
Exact same issue here, right down to returning IQueryable<T> and the SelectMany LINQ expression. I can get "one to one" relationships working fine, but not "one to many".

Ever figure this out?
@ShadowChaser: I did not understand what part is not working. Can you share more details or some code?
Mar 14, 2013 at 3:09 PM
Edited Mar 14, 2013 at 3:14 PM
In one of my controllers I added the following actions:
        // GET /RetailChannels(1)/Client
        public Client GetClient([FromODataUri] int key)
        {
            var entity =
            (
                from r in EntitySet
                where r.Id == key
                select new { r.Id, r.Client }
            ).SingleOrDefault();

            if (entity == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            return entity.Client;
        }

        // GET /RetailChannels(1)/RetailStores
        public IQueryable<RetailStore> GetRetailStores([FromODataUri] int key)
        {
            if (!EntitySet.Any(r => r.Id == key))
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            return EntitySet
                .Where(r => r.Id == key)
                .SelectMany(r => r.Stores);
        }
All three of the entities are registered with ODataConventionModelBuilder:
            modelBuilder.EntitySet<Client>("Clients");
            modelBuilder.EntitySet<RetailChannel>("RetailChannels");
            modelBuilder.EntitySet<RetailStore>("RetailStores");
If I access /odata/RetailChannels(1)/Client or /odata/RetailChannels(1)/RetailStores, a 501 is sent with the error "This service doesn't support OData requests in the form '~/entityset/key/unresolved'."

What's strange is that two of the relationships work, in a different controller. I can't find any difference between the implementations. Making a request to $metadata shows I have only one AssociationSet, not the 5 I expect. Perhaps I'm not following conventions properly?