Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
florian_moritz
Advisor
Advisor

What you will learn: use OData v4 to fetch data and explore the structure of the connected data model exposed by Graph.

Hello!

In this part of the tutorial on Graph we will focus on the OData protocol that Graph exposes and how to explore the structure of the data model of a business data graph. OData is one of the two data protocols supported by Graph - next to GraphQL.

For an overview of other parts of this series, check out the Information Map.

Graph is built on open standards and technologies. Clients can communicate with Graph using the Open Data v4 Protocol (OData). This is a standardized RESTful HTTP protocol with defined semantics that help promote interoperability between services.

OData is centered around resources (data entities), which are identified by URLs. In Graph, there are for example the Customer, Product, or SalesQuote entities. The structure of these entities is defined in an Entity Data Model (EDM) which can be inspected by clients. OData provides many operations to filter or search in entity collections, to navigate between associated entities and to adapt the response shape.

We will now look at examples on how to formulate OData requests for Graph.

 

Tutorial Setup

To keep things simple, we will be using the Graph sandbox business data graph (BDG). All you need to use it is your favorite HTTP tool, for example Postman, and your API key from SAP Business Accelerator Hub. To retrieve your API key, log into SAP Business Accelerator Hub, go to settings and copy your API key from the Show API Key button.

To make requests against the Graph sandbox BDG, add your API key as an HTTP header in your requests:
apiKey: <your API key>

Then you can use the Graph sandbox BDG through the following endpoint:

 

https://sandbox.api.sap.com/sapgraph/

 

Note: The API key is just used for the purposes of this sandbox endpoint and not relevant in the context of OData requests or productive Graph instances.

Exploring the OData data model

OData defines an Entity Data Model that describes the structure of all known entities. This helps developers and clients alike to reason about the available entities and their structure. The data model and further metadata can be inspected through a special $metadata resource. Graph makes metadata available for each namespace, such as sap.graph for the unified Graph data model.

To retrieve the metadata of the unified data model in Graph, make the following request, which will return an EDMX specification (an XML dialect for describing OData Entity Data Models).

 

https://sandbox.api.sap.com/sapgraph/sap.graph/$metadata

 

In the response, we can inspect the structure of all the entities (and sub-entities) that are described as EntityType entries as well as other metadata. Let's have a closer look at the definition of the SalesQuote entity type in this extract from the sales domain metadata:

 

<EntityType Name="SalesQuote">
    <Key>
        <PropertyRef Name="id"/>
    </Key>
    <Property Name="id" Type="Edm.String" MaxLength="82" Nullable="false"/>
    <Property Name="displayId" Type="Edm.String"/>
    <Property Name="netAmount" Type="Edm.Decimal" Scale="6" Precision="22"/>
    <Property Name="pricingDate" Type="Edm.DateTimeOffset"/>
    <Property Name="netAmountCurrency" Type="Edm.String" MaxLength="5"/>
    <NavigationProperty Name="_netAmountCurrency" Type="sap.graph.Currency"/>
    <NavigationProperty Name="items" Type="Collection(sap.graph.SalesQuote_items)" Partner="up_" ContainsTarget="true"/>
    <Property Name="soldToParty" Type="Edm.String" MaxLength="10"/>
    <NavigationProperty Name="_soldToParty" Type="sap.graph.Customer"/>
    <NavigationProperty Name="_cxsales" Type="sap.cxsales.SalesQuoteCollection"/>
    ...
</EntityType>

 

We can see that a SalesQuote has several properties of different types, such as String or Decimal, but also more complex structured types like the items property, which is a collection of several sap.graph.SalesQuote_items, that are also defined in the same data model. Furthermore, we see that the id property is referenced as key for this entity type. Navigation Properties allow for navigation to a related entity. The items property is a navigation property because the sap.graph.SalesQuote_items is modeled as a separate entity type in the metadata.

Let's look at an example. Add the following query path to the sandbox endpoint to make the request. This will retrieve one SalesQuote entity (here we add $top=1 to limit the result set to one entity):

 

/sap.graph/SalesQuote?$top=1

 

From inspecting the metadata, we have learned that SalesQuotes have items, however when comparing with the response from the example, no items are included in the response.

The reason for this is that OData has a mechanism for the client to control which navigation properties are included as part of the response. This is called expanding a property. By default, Graph returns responses non-expanded if not specified by the client. In addition to expanding the response, OData also allows for restricting it to requested values only. We will have a look at these mechanisms next.

Expanding and restricting the response

OData allows clients to adapt the response format in two ways. Clients can restrict the response to a set of specified properties via $select. And they can expand the response by including referenced entities inline as part of the response via $expand.

If we only require the netAmount of a SalesQuote, we can restrict the response by adding it to the $select query parameter:

 

/sap.graph/SalesQuote?$top=1&$select=netAmount

 

This will return only the netAmount property along with the id, as it is the key attribute and therefore always included.

If we also require the items of a SalesQuote, we need to expand the response (as it is a navigation property) by adding it to the $expand query parameter:

 

/sap.graph/SalesQuote?$top=1&$select=netAmount&$expand=items

 

This will result in the items array being returned as part of the SalesQuote response as illustrated here:

expand-items-1.png

If we have a look at a response snippet, this is what is being returned:

 

 

{
    "@odata.context": "$metadata#SalesQuote(netAmount,id,items())",
    "value": [
        {
            "id": "cxsales~1",
            "netAmount": 890,
            "items": [
                {
                    "itemId": "10",
                    "parentItemId": "",
                    "alternativeToItemId": "",
                    "itemCategory": "AGN",
                    "itemText": "Green Emission Calculator",
                    "product": "P300100",
                    "soldToPartyProductId": "",
                    "quantity": 1,
                    "quantityUnit": "EA",
                    "grossWeight": 0,
                    "grossWeightUnit": "",
                    "netWeight": 0,
                    "netWeightUnit": "",
                    "volume": 0,
                    "volumeUnit": "",
                    "plant": "",
                    "netAmount": 890,
                    "netAmountCurrency": "USD",
                    "pricingProduct": "",
                    "incotermsClassification": "",
                    "incotermsLocation": "",
                    "processingStatus": "1",
                    "cancellationReason": ""
                }
            ]
        }
    ]
}

 

Note that we do not have to specify expanded properties explicitly as part of $select.

We see that the item in the example above has a property product that references an id. When we inspect the metadata of the items by searching for the EntityType SalesQuote_items. We see that the product property represents the referenced product with a string value and the _product navigation property references the connected sap.graph.Product entity.

 

<EntityType Name="SalesQuote_items">
    <Key>
        <PropertyRef Name="itemId"/>
    </Key>
    <Property Name="itemId" Type="Edm.String" MaxLength="10" Nullable="false"/>
    <Property Name="itemText" Type="Edm.String"/>
    <Property Name="product" Type="Edm.String"/>
    <NavigationProperty Name="_product" Type="sap.graph.Product"/>
    ...
</EntityType>

 

To also include this _product as part of the response, we can expand it inside the already expanded items: a nested expand.

 

 

/sap.graph/SalesQuote?$top=1&$select=netAmount&$expand=items($expand=_product)

 

Which will include the _product response inline in the item of the SalesQuote response.

nested-expand-1.png

If we also want to adapt the format of the expanded product we can nest a $select query parameter in parentheses after the product. To restrict the nested product to the displayId, for example:

 

/sap.graph/SalesQuote?$top=1&$select=netAmount&$expand=items($expand=_product($select=displayId))

 

If we also want to select more properties, such as netAmountCurrency, we can just add them to the $select query parameter targeting the SalesQuote, separated by commas:

 

/sap.graph/SalesQuote?$top=1&$select=netAmount,netAmountCurrency&$expand=items($expand=_product($select=displayId))

 

The response for the SalesQuote with included netAmountCurrency, items and _product now looks like this:

 

{
    "@odata.context": "$metadata#SalesQuote(netAmount,netAmountCurrency,id,items(_product(displayId,id)))",
    "value": [
        {
            "id": "cxsales~1",
            "netAmount": 890,
            "netAmountCurrency": "USD",
            "items": [
                {
                    "itemId": "10",
                    "parentItemId": "",
                    "alternativeToItemId": "",
                    "itemCategory": "AGN",
                    "itemText": "Green Emission Calculator",
                    "product": "P300100",
                    "_product": {
                        "id": "cxsales~P300100",
                        "displayId": null
                    },
                    "soldToPartyProductId": "",
                    "quantity": 1,
                    "quantityUnit": "EA",
                    "grossWeight": 0,
                    "grossWeightUnit": "",
                    "netWeight": 0,
                    "netWeightUnit": "",
                    "volume": 0,
                    "volumeUnit": "",
                    "plant": "",
                    "netAmount": 890,
                    "netAmountCurrency": "USD",
                    "pricingProduct": "",
                    "incotermsClassification": "",
                    "incotermsLocation": "",
                    "processingStatus": "1",
                    "cancellationReason": ""
                }
            ]
        }
    ]
}

 

Working with collections: filtering, ordering and pagination

When working with collections of entities, we typically want to filter the entities by some criteria or arrange them in a specific order. In OData this is supported via the $filter and $orderby query parameters. With $top and $skip we can additionally define a sliding window over the result to implement pagination.

To continue with our example above, let's remove the $top=1 we added initially to now retrieve a collection of SalesQuotes. Say we want to retrieve all SalesQuotes with a value greater than 100 U.S. Dollars.

The value condition can be formulated with the greater equal operator: netAmount ge 100. For the currency condition we need to compare it with a string (enclosed in single quotes) for equality: netAmountCurrency eq 'USD'. We then combine these conditions with a logical and expression and add it to the example query below. We also define an ordering on the netAmount property with the $orderby query parameter, which is ascending by default.

Note: OData operators are lower-case and space-separated. This requires URL encoding. If you are using Postman for this tutorial you don't need to worry as Postman automatically applies URL encoding. 

 

/sap.graph/SalesQuote?$select=netAmount,netAmountCurrency&$expand=items($expand=_product($select=displayId))&$filter=netAmount ge 100 and netAmountCurrency eq 'USD'&$orderby=netAmount

 

Note: OData only supports writing filter expressions using a small vocabulary of filter operators. See here for a full list.

As a last step, we will now define a paging-window over all results. This can be achieved with the query parameters $top, which specifies how many result entities should be returned: the page size, and $skip, which defines how many result entities should be skipped from the beginning of the ordering. Hence, we describe the page size with $top and index the single pages with multiples of the page size in $skip. For the second page of five entities each, this would translate to our example as follows:

 

 

/sap.graph/SalesQuote?$select=netAmount,netAmountCurrency&$expand=items($expand=_product($select=displayId))&$filter=netAmount ge 100 and netAmountCurrency eq 'USD'&$orderby=netAmount&$top=5&$skip=5

 

Summary

In this tutorial we had a look at OData, one of the two data protocols used by Graph. We covered all the features that you as a developer working with Graph need to know, like how to:

  • formulate complex queries
  • inspect the metadata and structure of the unified data model
  • expand and restrict responses
  • work with collections

OData itself offers much more than what we showed in this tutorial. You can read more about it in the standard.

With Graph, you as a developer now have one data protocol you can use together with one API to retrieve data in one unified format, no matter the source system.

---
Florian Moritz