Use the OData v4 Client
In the following, the OData v4 features supported by the SAP Cloud SDK are explained.
For more details on how to execute requests using a (pre-)generated OData client refer to this documentation.
note
In the following code examples sometimes the business-partner service is used. This is a v2 service, but the listed examples are identical for OData v2 and v4.
#
GetAll Request BuilderThe GetAll request builder allows you to create a request to retrieve all entities that match the request configuration.
The example above creates a request to get all BusinessPartner
entities.
#
Select{" "}
When reading entities, the API offers select( ... )
on the builders.
Note that for OData v4 a select
does not automatically expand navigation properties.
See here for details on select and expand.
For non navigational property the select
behaves as follows:
The above translates to the following query parameters:
#
Filter{" "}
When operating on a collection of entities the API offers filter( ... )
on the builders.
It directly corresponds to the $filter
parameter of the request.
Filters are also built via the static property fields on entities:
The example above will translate to this filter parameter:
Take note of the order of and
and or
.
As or
is invoked on the result of and
it will form the outer expression while and
is an inner expression in the first branch of or
.
It is also possible to pass multiple filters to the same filter function without concatenating them with and
or or
.
They will be concatenated with and
by default.
The two following examples are equal:
The example above can be shortened to:
#
Filter on One-to-One Navigation Properties{" "}
In addition to basic properties, filters can also be applied on one-to-one navigation properties.
The example below shows how to filter on the TO_CUSTOMER
, which is a one-to-one navigation property of the BusinessPartner entity.
Please note, the CUSTOMER_NAME
and CUSTOMER_FULL_NAME
are properties of the entity Customer
, which is the type of the one-to-one navigation property TO_CUSTOMER
.
The generated $filter
will be:
#
Filter on One-to-Many Navigation Properties{" "}
OData V4 introduces lambda operators e.g., any
/all
, so that the root property of the one-to-many navigation properties can be filtered.
Below is an example that demonstrates how to use the lambda operator any.
The generated $filter
parameter of the URL will be:
#
More Filter ExpressionsMore advanced filter expressions can be found here.
#
Expand#
Expand and Select{" "}
In contrast to the OData v2 implementation, you have to select
and expand
separately.
In other words selected properties are not expanded automatically as in v2.
The reason for this difference originates in the way select and expand work in OData v4.
In OData v4 you select within the expand argument $expand=Friends($select=FirstName)
whereas in OData v2 you select via a path $select=Friends/FirstName&$expand=Friends
.
That's why we mimic this behavior for select
and expand
operations in our API for OData v4 type-safe client.
In the example above you select the LAST_NAME
of the root entity and expand the navigation property FRIENDS
.
In the expanded entity the selected fields are FIRST_NAME
and ADDRESS_INFO
.
The generated URL for this request will be:
If no select
is given, all non-navigational properties are included in the response.
#
Sub-Queries in Expand{" "}
Note that you can create very complex queries within the expand scope:
In this example, the filter will reduce the friends to be shown.
The effect of a filter depends on whether it is used in or outside of an expand
.
The different cases are explained in Filters in Expand.
The URL for the query will be:
#
Filter Parent vs Filter Children{" "}
Depending on the context of the filter it will either filter the parent or the children.
In our example, we have a PERSON
related to zero to N FRIENDS
which are both of type people.
If you want to get all people with first name John
the query is:
If you want to get all people who have at least one friend with first name John
the query is:
The lambda all would enforce that all friends must have first name John
.
The two queries above filter the parent entity person.
In case you want to get all people but reduce the friends in the response, the filter has to be inside the expand:
This will return all people but only the friends with the first name John
will be included in the response.
#
Skip{" "}
skip
allows you to skip a number of results in the requested set.
It can be useful for paging:
#
Top{" "}
top
limits the number of returned results.
This can also be useful for paging:
The example above retrieves the first ten BusinessPartner
entities.
#
Count{" "}
The method count()
allows you to get the number of elements in a collection.
It is only available for getAll()
requests and is added before the request execution:
The return type of count requests is a Promise<number>
.
You can combine the count()
with filter conditions.
To get the number of business partners with first name John
execute the following request:
As defined in the OData spec count
is not affected by top
, skip
, and orderBy
.
top()
and skip()
are ignored for count
If you include these methods in a count request they will be ignored by the SAP Cloud SDK. So these three requests:
will all return the same value.
Using count within a filter is not supported
Note that we currently do not support the usage of count within a filter condition as described in the OData v4 specification.
#
GetByKey Request BuilderThe GetByKey request builder allows you to create a request to retrieve one entity based on its key:
The example above retrieves the BusinessPartner
with the id 'id'
.
The result can be restricted by applying the select function, same as in the GetAll request.
#
Create Request BuilderThe Create request builder allows you to send a POST
request to create a new entity:
In the example above we created an instance of BusinessPartner
and sent it to the BusinessPartner
service in a POST
request.
#
Deep Create{" "}
It is also possible to create an entity together with related entities in a single request:
Troubleshooting
When you try the example code above for testing the deep creat
feature, you might see some errors like "operation module BUA_CHECK_ADDRESS_VALIDITY_ALL; a check table is missing"
.
Typically, it can happen if you are using a new system with a default configuration.
You need to configure an address usage
field as shown in the example below to fix it.
You can also create an entity asChildOf
another entity.
#
Create as Child Of{" "}
Assume you have already created a business partner and would like to add a new address to it:
This can be done by using the asChildOf
method which allows creating an entity as a child of an existing entity.
You need to give the parent object and the field connecting the two entities:
#
Update Request BuilderThe Update request builder allows you to send PUT
or PATCH
requests.
By default PATCH
is used to only update the changed fields:
In the example above only the changed fields of the given businessPartner
instance are sent to the BusinessPartner
service using PATCH
.
To update the whole entity use replaceWholeEntityWithPut
:
This will send a PUT
request and thereby replace the whole entity.
Entities can only be updated if ETags match.
If you want to force an update of the entity regardless of the ETag configure the request to ignore version identifiers with ignoreVersionIdentifier
:
#
Delete Request BuilderThe Delete request builder allows you to create DELETE
requests, that delete an entity.
Entities can only be deleted if ETags match.
If you want to force deletion of the entity regardless of the ETag configure the request to ignore version identifiers with ignoreVersionIdentifier
:
You can also overwrite ETags using setVersionIdentifier
:
In the example above the ETag 'ETag' is being used instead of the original one.
#
Handling of ETags#
Handling of Cross-Site Request Forgery TokensTo create, update, and delete requests the SAP Cloud SDK will try to send a CSRF token. Upon execution, the request will try to fetch a token first before issuing the create request. Many services require this behavior for security reasons. However, the create request will be made without a CSRF token if none could be obtained.
#
Available Filter Expressions#
Filter Functions{" "}
There are predefined filter functions e.g. length
, substring
, substringOf
in the core library, that allow for a wide range of filter expressions:
#
Function Imports#
Request Builder{" "}
The function imports request builder helps build a request for a service operation containing parameters in a type-safe way.
This time, as an example, the Warehouse Outbound Delivery Order Service
is used, because not all the services contain function imports like the Business Partner Service
.
The type-safe client for the Warehouse Outbound Delivery Order Service
can be found in the package @sap/cloud-sdk-vdm-warehouse-outbound-delivery-order-service
.
The example below creates a function import request builder for the service operation PostGoodsIssue
and then execute it against your service.
The service operation is defined in the service metadata.
#
Setting ETag{" "}
The ETag
handling with the function imports is not integrated.
Below is an example demonstrating how to make use of the withCustomHeaders
for setting ETag
by your own.
In order to use versionIdentifier
for ETag
value, make sure you fetch the entity information via e.g., a get
request.
#
Known Issues{" "}
- Currently, the
Entity Type
is not supported to be used as the parameters of the function import. Function imports with such unsupported parameters are ignored during the generation. This feature will be implemented in the future. Please check this issue and comment if you need this feature. - Also, for the time being, we only support
unbound functions
. Thebound functions
will be supported later, you can create a feature request here.
#
Action Imports{" "}
Similar to the function import mentioned above, the action import request builder has the same structure, so you can build action requests the same way as the function import.
#
Known Issues{" "}
- Similar to the
function import
, theaction import
does not supportEntity Type
as parameters. - Also, we only support
unbound actions
for now. We plan to supportbound actions
in the future, please create a feature request here.
#
Batch RequestsOData batch requests combine multiple operations into one POST operation, allowing you to execute multiple requests with just one network call. This can significantly reduce the network overhead you have to deal with when you want to execute a large number of requests.
An OData batch request can consist of a number of retrieve requests and changesets. Those can be combined arbitrarily.
#
Retrieve Request{" "}
A retrieve request is any HTTP GET
request.
In terms of the SAP Cloud SDK this includes all requests built by a GetAllRequestBuilder and GetByKeyRequestBuilder.
Retrieve requests can be passed directly to the batch
function, which in turn can be executed once to execute all subrequests.
Once a batch request is executed it returns a list of BatchResponse
s.
Those contain the raw response information of each subrequest, the subresponse to a retrieve subrequest can either be a ReadResponse
or an ErrorResponse
.
To determine if a request was successful use .isSuccess()
.
Successful requests can be cast to ReadResponse
which contains the HTTP code, the raw body, and the constructor of the entity that was parsed from the response.
To work with an instance of the retrieved entity, you can use the .as
method, which allows you to transform the raw data into an instance of the given constructor.
Note, that retrieve responses can be ErrorResponse
s.
Therefore, it is crucial to check responses for success, before casting them to ReadResponse
.
In the example below, each given address id is mapped to a GetByKeyRequestBuilder.
These retrieve requests are combined into one batch
request and executed against a destination.
If one of the requests was not successful, an error will be thrown, otherwise, the responses are transformed into instances of BusinessPartnerAddress
.
#
Changeset{" "}
A changeset is a collection of HTTP POST
, PUT
, PATCH
and DELETE
operations - requests built by any CreateRequestBuilder, UpdateRequestBuilder and DeleteRequestBuilder in terms of the SAP Cloud SDK.
The order of execution within a changeset is not defined.
This differs from the whole batch request itself, where the order is defined.
Therefore the requests within a changeset should not depend on each other.
If the execution of any of the requests within a changeset fails, the whole changeset will be reflected as an error in the response and will not be applied, much like a database transaction.
Change requests cannot be passed to a batch request directly.
They have to be combined in a changeset, which in turn can be passed to the batch request.
Once a batch request is executed it returns a list of BatchResponse
s.
Those contain the raw response information of each subrequest.
The response to a changeset request can either be a collection of the subresponses to the subrequests of the changeset of type WriteResponses
or an ErrorResponse
.
To determine if a request was successful use .isSuccess()
.
Successful requests should be cast to WriteResponses
which contains all subresponses for the changeset request.
Those responses can be accessed by .responses
and have the type WriteResponse
.
Each WriteResponse
contains the HTTP code and can contain the raw body and the constructor of the entity that was parsed from the response, depending on whether there was a body in the response.
Create and delete requests typically do not have a response body.
To work with an instance of an entity given in a WriteResponse
, you can use the .as
method, which allows you to transform the raw string body into an instance of the given constructor.
Note that the response may not exist, so you should only call this method if you know that there is data.
Typically the HTTP code is a good indicator for this (201 No Content
probably won't have content).
If you are working with TypeScript you will have to tell the compiler, that the .as!
method can be used here by adding a !
.
Also note, that retrieve responses can be ErrorResponse
s.
Therefore, it is crucial to check responses for success, before casting them to WriteResponses
.
In the example below, a list of addresses is mapped to UpdateRequestBuilders.
These change requests are combined to one changeset
, which is passed to the batch
request and executed against a destination.
Once the batch request is executed it returns a list of BatchResponse
s, which in this example contains one response only, namely the one for the changeset.
If the request was not successful, an error will be thrown, otherwise, the subresponses are transformed into instances of BusinessPartnerAddress
.
#
Combining Requests{" "}
#
Serialization{" "}
By default when you execute a batch request, the subrequests are serialized to a multipart representation of the request, which is essentially a string. This is what a create request for a business partner addresses would serialize to:
The first lines are request headers for the multipart request, followed by a blank line.
The next line contains the request method and URL, followed by the request headers, a blank line, and the request payload.
Every "atomic" request is serialized to a string of this kind, while GET
and DELETE
requests do not provide a payload.
#
Configure Subrequest Serialization{" "}
By default, URLs in the multipart representation of a request are serialized to a path relative to the service, e.g.:
However, some services might only understand URLs relative to the entity or even absolute URLs.
To configure the serialization of the URLs within a batch request, you can set the subrequest path type with withSubRequestPathType
.
You can either set it to 'relativeToService'
, as is the default, 'relativeToEntity'
, which will yield URLs relative to the entity or 'absolute'
, which will produce absolute URLs.
See below for examples:
Serialize subrequest path relative to entity:
Serialize subrequest path as absolute URL: