Expand does not work with GetEntityByKey

Topics: ASP.NET Web API
Jun 4, 2013 at 8:32 AM
Hi,

I use expand with the nightly builds, it works well when calling it this way:

http://../odata/Incidents?$filter=IncidentID eq 780&$expand=IncidentComments

but when calling it this way:

http://../odata/Incidents(780)?$expand=IncidentComments

I get the Incident entity without the IncidentComments entity.

I have

[Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]

on both "Get" and "GetEntityByKey" in the controller.


any idea?
Coordinator
Jun 4, 2013 at 3:35 PM
Typically the request for a specific entity is handled by a separate action. Have you marked both of your Get actions including the one that takes a key parameter as [Queryable]?

Daniel Roth
Jun 4, 2013 at 3:54 PM
Hi Daniel,

As I said' I have [Queryable(AllowedQueryOptions = AllowedQueryOptions.All)] on both "Get" and "GetEntityByKey".

I guess the issue is with the returned value of GetEntityByKey which is not IQueryable, but the entity it self, so I have to include the Odata query manually in the implemetation of GetEntityByKey somehow?

here is my current implementation:
[Queryable(AllowedQueryOptions=AllowedQueryOptions.All)]
        public override IQueryable<Incident> Get()
        {
            return _LINQ.IncidentQuery;
        }

        [Queryable(AllowedQueryOptions = AllowedQueryOptions.All)]
        protected override Incident GetEntityByKey(int key)
        {
            return _LINQ.IncidentQuery.FirstOrDefault(p => p.IncidentID == key);
        }
Jun 5, 2013 at 3:48 PM
What is your backend? Is it entity framework? If so, it is expected as returning Incident implies that all query context(IQueryable<T>) is lost. So, QueryableAttribute cannot ask for the related IncidentComments from the backend database.

Hence, we added a new class SingleResult<T> that wraps around IQueryable<T>(query context) and yet tells the framework that the response is a single entity and not a collection. Check out the sample 2 here for hpw to return SingleResult<T>.
Jul 5, 2013 at 8:30 PM
Raghuramn,

It appears that Daniel was using a controller derived from EntitySetController, which suggests that the return type of the actual Get method (which internally calls GetEntityByKey) would have been an HttpResponseMessage. I have not been able to figure out how to adapt your "Sample 2" code to this situation. In another thread, you advised someone to derive directly from ODataController rather than EnitySetController to overcome a similar issue but I haven't been able to get that to work either. In my experience it comes down to two options, neither of which works. If the return type of the Get is an HttpResponseMessage, the $expand is ignored (even if the content of the response is an IQueryable). On the other hand, if the return type is an IQueryable then Web API/Odata doesn't seem to want to use the method and instead calls my parameterless Get. I know the documentation/samples are thin for this but would it be possible to provide a sample that utilizes a controller derived from ODataController (or, possibly EntitySetController) that demonstrates how to get "~/entityset/key" Odata paths to work with $expand without relying on $filter?

Thanks!
-JLS
Jul 5, 2013 at 9:48 PM
public class CustomersController : ODataController
{
    [Queryable]
    public SingleResult<Customer> GetCustomer([FromODataUri]int key)
    {
        return SingleResult.Create(_dbContext.Customers.Where(c => c.ID == id);
    }
}
should work.
Jul 8, 2013 at 12:43 PM
Raghuramn,

Thanks for the sample. Unfortunately, with my controller set up the way you suggest, I get the following response:

No routing convention was found to select an action for the OData path with template '~/entityset/key'.

for a request URI that looks like: http://localhost:54710/odata/WorkOrders(guid'5139945F-11A6-4E10-B369-6143C77F08C3')

However, using the same URI, if I replace the "Get" from your sample with the following "Get", the service returns a WorkOrder, as expected:
    public HttpResponseMessage Get([FromODataUri] Guid key)
    {
        IQueryable<WorkOrder> entity = _db.WorkOrders.Where(wo => wo.WorkOrderGUID == key);
        return GetByKeyResponse(Request, entity);
    }

    private HttpResponseMessage GetByKeyResponse<WorkOrder>(HttpRequestMessage request, IQueryable<WorkOrder> entity)
    {
        if (entity == null)
        {
            return request.CreateResponse(HttpStatusCode.NotFound);
        }
        else
        {
            return request.CreateResponse(HttpStatusCode.OK, entity);
        }
    }
Of course, $expand doesn't work with this because the response is an HttpResponseMessage, which is not an IQueryable. In case it matters, here is the portion of my WebApiConfig that I believe is relevant:
        ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
        modelBuilder.EntitySet<Models.WorkOrder>("WorkOrders");
        modelBuilder.EntitySet<Models.WorkOrderDetail>("WorkOrderDetails");
        modelBuilder.EntitySet<Models.Activity>("Activities");
        modelBuilder.EntitySet<Models.ChecklistItem>("ChecklistItems");
        modelBuilder.EntitySet<Models.Labor>("Labor");
        modelBuilder.EntitySet<Models.WOEquip>("WOEquip");
        Microsoft.Data.Edm.IEdmModel model = modelBuilder.GetEdmModel();
        config.Routes.MapODataRoute("ODataRoute", "odata", model);

        // Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
        // To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
        // For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
        config.EnableQuerySupport();
Any idea where I am going wrong?

-JLS
Jul 19, 2013 at 12:56 PM
I am having the same issue. Basically there is no guidance on how this should work with EntitySetController. All examples out there for basic OData crud use EntitySetController but the recommendation by Raghuramn is to use ODataController when using Expand on a single entity. EntitySetController does not support SingleResult.
Jul 24, 2013 at 3:55 AM
It would be nice to have an EntitySetController flavour that supported $expand nicely. I'm thinking I'll probably just go to the source and grab it myself and make an EntitySetExpandController or something like that. However it'd be nice to know if something better was in the pipeline as I'd prefer to stick to mainstream eventually supported stuff from Microsoft's devs rather than my own mini fork.
Sep 10, 2013 at 7:36 AM
Edited Sep 11, 2013 at 3:18 AM

OData Get(key) for EntitySetController and AsyncEntitySetController

Senarios with the MusicStore db using Microsoft.AspNet.WebApi.OData 5.0.0-rtm-130910
/Album(key) -
/Album(key)?$select=Price,Title,Genre&$expand=Genre

EntitySetController
public override HttpResponseMessage Get([FromODataUri] int key)
{
    var singleResult = SingleResult.Create(db.Albums.Where(x => x.Id == key));

    // Create an HttpResponseMessage and add singleResult
    return Request.CreateResponse(HttpStatusCode.OK, singleResult);
}
AsyncEntitySetController
public override async Task<HttpResponseMessage> Get([FromODataUri] int key)
{
    var singleResult = SingleResult.Create(db.Albums.Where(x => x.Id == key));

    // Create an HttpResponseMessage and add singleResult
    return Request.CreateResponse(HttpStatusCode.OK, singleResult);
}
Sep 10, 2013 at 5:06 PM
what is your nuget package version for Microsoft.Aspnet.WebApi.OData?
Sep 10, 2013 at 9:44 PM
Edited Sep 11, 2013 at 3:09 AM
Microsoft.AspNet.WebApi.OData 5.0.0-rtm-130910
Sep 10, 2013 at 10:03 PM
The RC package had a bug with SingleResult that we fixed later. https://aspnetwebstack.codeplex.com/workitem/1217

Could you please try upgrading to our nightly builds and see if it fixes for you?
Sep 11, 2013 at 3:06 AM
Excellent!
It works with the 5.0.0-rtm-130910 nightly Dev build (have not tried nightly release build yet).
Thank you very much for the fix and the quick response. I updated my post above.