1
votes

In OData V3, I can select just fields from parent/ancestor entities like this: http://services.odata.org/V3/Northwind/Northwind.svc/Order_Details(OrderID=10248,ProductID=11)?&$select=Product/Category/CategoryName&$expand=Product/Category

That query returns only CategoryName, it does not include any fields from Order_Details or Product. This behavior is very important to our application for performance reasons. Selecting all fields when we don't need them can have a significant impact on query performance.

There does not seem to be a way to accomplish the same in OData V4. The equivalent query returns all fields from Order_Details and Product http://services.odata.org/V4/Northwind/Northwind.svc/Order_Details(OrderID=10248,ProductID=11)?$expand=Product($expand=Category($select=CategoryName))

The closest I can get is to just select one field from each level, introduces a lot of complexity into our code, and it has been difficult to ensure that all queries (future and existing) adhere to this rule.

3

3 Answers

1
votes

The syntax for this is:

http://services.odata.org/V4/Northwind/Northwind.svc/Order_Details(OrderID=10248,ProductID=11)/Product?$expand=Category($select=CategoryName)

The key is to navigate to the path of the parent product resource from the order:

~/Order_Details(OrderID=10248,ProductID=11)/Product

and then apply your expansion on that resource. See 4.3 Addressing Entities

These rules are recursive, so it is possible to address a single entity via another single entity, a collection via a single entity and even a collection via a collection; examples include, but are not limited to:

  • By following a navigation from a single entity to another related entity (see rule: entityNavigationProperty)
    Example 14: http://host/service/Products(1)/Supplier
0
votes

The simplest solutions would be to create View with required schema on your db server and try to fetch data from this datasource with filters and column name(s) instead.

Especially when facing issues with performance.

The best way would be to register this class to your IoC as singleton

public class InternalODataEdmModelBuilder
{
    private readonly ODataConventionModelBuilder _oDataConventionModelBuilder = new ODataConventionModelBuilder();
    private IEdmModel _edmModel;

    public InternalODataEdmModelBuilder()
    {
        ODataEntitySetsConfigInternal.Register(_oDataConventionModelBuilder);
    }

    // cache
    public IEdmModel GetEdmModel()
    {
        return _edmModel ?? (_edmModel = _oDataConventionModelBuilder.GetEdmModel());
    }
}

internal static class ODataEntitySetsConfigInternal
{
    public static void Register(ODataConventionModelBuilder oDataModelBuilder)
    {
        if (oDataModelBuilder == null)
        {
            throw new Exception("'ODataConventionModelBuilderWebApi' cannot be null");
        }

        oDataModelBuilder.EntitySet<YourView>("YourView").EntityType.HasKey(x => x.YourKey);
    }
}

And then inject this registered object in your API controller and build your query from URL like this:

        ODataQueryContext queryContext = new ODataQueryContext(_oDataConventionModel, typeof(YourViewEFType), null);
        var option = new ODataQueryOptions(queryContext, Request);
        var data = option.ApplyTo(data, new ODataQuerySettings { EnsureStableOrdering = false });

And then map data into your POCO (API EDM model shown to the public world).

Hope this helps.

0
votes

The closest I can get is to just select one field from each level, introduces a lot of complexity into our code, and it has been difficult to ensure that all queries (future and existing) adhere to this rule.

Looks something like this:

http://services.odata.org/V4/Northwind/Northwind.svc/Order_Details(OrderID=10248,ProductID=11)?$expand=Product($select=Category;$expand=Category($select=CategoryName))&$select=Product

There is certainly a bit of added complexity here, but this was acceptable in my case.