This section covers how to use the OData API to perform common queries, create/update/delete (CUD) operations, and service operations. Each section includes code for using the generated proxy classes with Dynamic API, followed by another code snippet using Dynamic API alone.
An instance of the
DataService
class is required to interact with the OData endpoint that it represents. It relies on parsed metadata from the service document provided by the OData endpoint. For proxy classes, a generated service class that is a subclass of
DataService
should be used.
Every query requires the construction of a corresponding
DataQuery
, passed as a parameter to the
DataService
executeQuery
method. With proxy classes, you can use the getter methods for the entity set provided by the generated service class. Internally, those getter methods make use of
DataQuery
and
executeQuery
in their implementation. It is therefore important to have a good understanding of the
DataQuery
class to query an OData service provider.
The first couple of code samples include the lookup of the entity set, entity type, and property required for parameters when used without proxy classes. Subsequent samples skip these lookups to make the code easier to read. The following variable naming convention is used for entity set, entity type, and properties.
Entity type: <Entity Type Name>Type e.g.
eventType
Entity Set: <Entity Type Name>Set e.g.
eventSet
Property: <Entity Type Name><Property Name>Property e.g.
eventEventIDProp
// OData Query: /Events// Proxy Class// Use generated service class getter method for collection/entity set// Implementation code creates a DataQuery to indicate the entity set to be queriedList<Event>events=eventService.getEvents();for(Eventevent:events){StringeventName=event.getName();// Get the start date time of the eventLocalDateTimestartDateTime=event.getStartDateTime();// Dynamic API// Use DataService to look up the entity set with the name of the entity set element from the service document// Get the entity setEntitySeteventSet=dataService.getEntitySet("Events");// Get the type of the entityEntityTypeeventType=eventSet.getEntityType();// Use the from method of DataQuery to specify the entity set to retrieve fromDataQueryquery=newDataQuery().from(eventSet);EntityValueListevents=dataService.executeQuery(query).getEntityList();// Get event name value from resultPropertyeventNameProp=eventType.getProperty("Name");PropertyeventStartDateTimeProp=eventType.getProperty("StartDateTime");for(EntityValueevent:events){StringeventName=eventNameProp.getString(event);LocalDateTimestartDateTime=LocalDateTime.castRequired(eventStartDateTimeProp.getValue(event));
// OData Query: /Events// Proxy Class// Use generated service class getter method for collection/entity set// Implementation code creates a DataQuery to indicate the entity set to be queriedvalevents=eventService.eventsfor(eventinevents){valeventName=event.name// Get the start date time of the eventvalstartDateTime=event.startDateTime// Dynamic API// Use DataService to look up the entity set with the name of the entity set element from the service document// Get the entity setvaleventSet=dataService.getEntitySet("Events")// Get the type of the entityvaleventType=eventSet.entityType// Use the from method of DataQuery to specify the entity set to retrieve fromvalquery=DataQuery().from(eventSet)valevents=dataService.executeQuery(query).entityList// Get event name value from resultvaleventNameProp=eventType.getProperty("Name")valeventStartDateTimeProp=eventType.getProperty("StartDateTime")for(eventinevents){valeventName=eventNameProp.getString(event)valstartDateTime=LocalDateTime.castRequired(eventStartDateTimeProp.getValue(event))
With the generated service class, you don't have to specify the entity set from which retrieval is to take place as it is already in its implementation. In addition, a native list of strongly typed class instead of a generic EntityValueList is returned.
The client determines the size of a page and uses the skip and top query options to request a specific page. This is known as client-driven paging.
// OData Query: /Events?$skip=100&$top=50// Page size = 50, reading third page// Proxy Class// Use a query for getEventsDataQueryquery=newDataQuery().skip(100).top(50);List<Event>events=eventService.getEvents(query);// Dynamic APIDataQueryquery=newDataQuery().from(eventSet).skip(100).top(50);EntityValueListevents=dataService.executeQuery(query).getEntityList();
// OData Query: /Events?$skip=100&$top=50// Page size = 50, reading third page// Proxy Class// Use a query for getEventsvalquery=DataQuery().skip(100).top(50)valevents=eventService.getEvents(query)// Dynamic APIvalquery=DataQuery().from(eventSet).skip(100).top(50)valevents=dataService.executeQuery(query).entityList
Please be aware that if the membership of the collection is changing, paging may return an entity twice or not at all. This is how paging works against a changing collection.
InlineCount is a system query option that indicates that the response to the request includes a count of the number of entities satisfying the condition in the filter query option. If no filter query option is specified, it is assumed to be the entire collection. InlineCount is particular useful for client-driven paging to determine the total number of pages of entities satisfying the query, allowing it to prepare UI for a user to retrieve any one of the pages.
// OData: /Events?$inlinecount=allpages&$top=50&$skip=0// Proxy Class// When using executeQuery instead of getEvents or getEvent we need to// specify which entity set the query is running againstDataQueryquery=newDataQuery().from(EntitySets.events).skip(0).top(50).inlineCount();QueryResultresult=eventService.executeQuery(query);longcount=result.getInlineCount();// a list of 50 events will be returnedList<Event>events=Event.list(result.getEntityList());// Dynamic APIDataQueryquery=newDataQuery().from(eventSet).skip(0).top(50).inlineCount();QueryResultresult=dataService.executeQuery(query);longcount=result.getInlineCount();// a list of 50 events will be returnedEntityValueListevents
=result.getEntityList();
// OData: /Events?$inlinecount=allpages&$top=50&$skip=0// Proxy Class// When using executeQuery instead of getEvents or getEvent we need to// specify which entity set the query is running againstvalquery=DataQuery().from(EntitySets.events).skip(0).top(50).inlineCount()valresult=eventService.executeQuery(query)valcount=result.inlineCount// a list of 50 events will be returnedvalevents=Event.list(result.entityList)// Dynamic APIvalquery=DataQuery().from(eventSet).skip(0).top(50).inlineCount()valresult=dataService.executeQuery(query)valcount=result.inlineCount// a list of 50 events will be returnedvalevents=result.entityList
In server-driven paging, the server determines how much to send back for a request. It is possible to influence the server by specifying a page size preference. However, it is up to the server whether it will honor the setting. At the end of the server response is a nextLink URL for use by the client to fetch the next page of entities.
It is possible to get a count of the total number of entities via the InlineCount query option. However, the client can only read the next page via nextLink.
// Proxy ClassDataQuerypagedQuery=newDataQuery().from(EntitySets.events).page(10);QueryResultresult=eventService.executeQuery(pagedQuery);List<Event>events=Event.list(result.getEntityList());// Get the query based on the nextLink URI returned by the serverpagedQuery=result.getNextQuery();// Ready to query the server again for the next page of entities// Dynamic APIDataQuerypagedQuery=newDataQuery().from(eventSet).page(10);QueryResultresult=dataService.executeQuery(pagedQuery);EntityValueListevents=result.getEntityList();// Get the query based on the nextLink URI returned by the serverpagedQuery=result.getNextQuery();// Ready to query the server again for the next page of entities
// Proxy ClassvarpagedQuery=DataQuery().from(EntitySets.events).page(10)valresult=eventService.executeQuery(pagedQuery)valevents=Event.list(result.entityList;// Get the query based on the nextLink URI returned by the serverpagedQuery=result.nextQuery// Ready to query the server again for the next page of entities// Dynamic APIvarpagedQuery=newDataQuery().from(eventSet).page(10)valresult=dataService.executeQuery(pagedQuery)valevents=result.entityList// Get the query based on the nextLink URI returned by the serverpagedQuery=result.nextQuery// Ready to query the server again for the next page of entities
The entity key is required to access a particular instance of the entity set. The getter method takes a DataQuery constructed with the entity key as the parameter.
// OData Query: /Events(1000L)// Proxy ClassDataQueryquery=newDataQuery().withKey(Event.key(1000L));Eventevent=eventService.getEvent(query);// Dynamic APIDataValuekeyValue=LongValue.of(1000L);// Note: composite key can be created using multiple with methods// Assume Session requires a composite key of EventID and SessionID// new EntityKey().with("EventID", keyValue).with("SessionID", idValue)EntityKeyeventKey=newEntityKey().with("EventID",keyValue);DataQueryquery=newDataQuery().from(eventSet).withKey(eventKey);EntityValueevent=dataService.executeQuery(query).getRequiredEntity();
// OData Query: /Events(1000L)// Proxy Classvalquery=DataQuery().withKey(Event.key(1000L))valevent=eventService.getEvent(query)// Dynamic APIvalkeyValue=LongValue.of(1000L)// Note: composite key can be created using multiple with methods// Assume Session requires a composite key of EventID and SessionID// new EntityKey().with("EventID", keyValue).with("SessionID", idValue)valeventKey=EntityKey().with("EventID",keyValue)valquery=DataQuery().from(eventSet).withKey(eventKey)valevent=dataService.executeQuery(query).requiredEntity
If there is no event with the specified key, an exception will be thrown. This is due to the getRequiredEntity method used in the implementation of getEvent in the generated service class. With Dynamic API, it is possible to return null by using getOptionalEntity.
You can create a filter to retrieve the subset of a collection that satisfies its criteria. An empty collection will be returned if there is no entity within the collection that satisfies the criteria.
The withKey method of DataQuery essentially generates a filter using the entity key.
// OData Query: /Events?$filter=Name eq 'SAP Tech Ed 2015'// Proxy ClassDataQueryquery=newDataQuery().filter(Event.name.equal("SAP Tech Ed 2015"));List<Event>events=eventService.getEvents(query);// Dynamic APIDataQueryquery=newDataQuery().from(eventSet).filter(eventNameProp.equal("SAP Tech Ed 2015"));EntityValueListevents=dataService.executeQuery(query).getEntityList();
// OData Query: /Events?$filter=Name eq 'SAP Tech Ed 2015'// Proxy Classvalquery=DataQuery().filter(Event.
name.equal("SAP Tech Ed 2015"))valevents=eventService.getEvents(query)// Dynamic APIvalquery=DataQuery().from(eventSet).filter(eventNameProp.equal("SAP Tech Ed 2015"))valevents=dataService.executeQuery(query).entityList
For complex filtering criteria, consider using the QueryOperator within the filter.
// Find all sessions with registration >= 90%// Proxy ClassDataQueryquery=newDataQuery().filter(QueryOperator.multiply(Session.participants,IntValue.of(100)).divide(Session.maxParticipants).greaterEqual(IntValue.of(90)));List<Session>sessions=eventService.getSessions(query);// Dynamic APIDataQueryquery=newDataQuery().from(sessionSet).filter(QueryOperator.multiply(sessionParticipantsProp,IntValue.of(100)).divide(sessionMaxParticipantsProp).greaterEqual(IntValue.of(90))EntityValueListsessions=dataService.executeQuery(query).getEntityList();
// Find all sessions with registration >= 90%// Proxy Classvalquery=DataQuery().filter(QueryOperator.multiply(Session.participants,IntValue.of(100)).divide(Session.maxParticipants).greaterEqual(IntValue.of(90)))valsessions=eventService.getSessions(query)// Dynamic APIvalquery=DataQuery().from(sessionSet).filter(QueryOperator.multiply(sessionParticipantsProp,IntValue.of(100)).divide(sessionMaxParticipantsProp).greaterEqual(IntValue.of(90))valsessions=dataService.executeQuery(query).entityList
Count the number of entities in a collection, or the number of entities that satisfies a specific query. Note that the list of qualifying events is not returned, only the count.
To expand, specify a navigation property to retrieve associated entities. For example, when we retrieve an event, we can have all the sessions belonging to the event be returned inline.
// OData: /Events?$filter=Name eq 'SAP Tech Ed 2015'&$expand=Sessions// Proxy ClassDataQueryquery=newDataQuery().filter(Event.name.equal("SAP Tech Ed 2015")).expand(Event.sessions);List<Event>events=eventService.getEvents(query);// Dynamic APIPropertyeventSessionsProp=eventType.getProperty("Sessions");DataQueryquery=newDataQuery().from(eventSet).filter(eventNameProp.equal("SAP Tech Ed 2015")).expand(eventSessionsProp);EntityValueListevents=dataService.executeQuery(query).getEntityList();
// OData: /Events?$filter=Name eq 'SAP Tech Ed 2015'&$expand=Sessions// Proxy Class
valquery=DataQuery().filter(Event.name.equal("SAP Tech Ed 2015")).expand(Event.sessions)valevents=eventService.getEvents(query)// Dynamic APIvaleventSessionsProp=eventType.getProperty("Sessions")valquery=DataQuery().from(eventSet).filter(eventNameProp.equal("SAP Tech Ed 2015")).expand(eventSessionsProp)valevents=dataService.executeQuery(query).entityList
You can expand with multiple properties by passing a comma-separated list of navigation properties to expand or invoking expand multiple times. The following code fragments demonstrate how to do this using proxy classes.
// OData: /Events?$filter=Name eq 'SAP Tech Ed 2015'&$expand=Sessions,Features// Proxy ClassDataQueryquery=newDataQuery().filter(Event.name.equal("SAP Tech Ed 2015")).expand(Event.sessions,Event.features);List<Event>events=eventService.getEvents(query);DataQueryquery=newDataQuery().filter(Event.name.equal("SAP Tech Ed 2015")).expand(Event.sessions).expand(Event.features);List<Event>events=eventService.getEvents(query);
// OData: /Events?$filter=Name eq 'SAP Tech Ed 2015'&$expand=Sessions,Features// Proxy Classvalquery=DataQuery().filter(Event.name.equal("SAP Tech Ed 2015")).expand(Event.sessions,Event.features)valevents=eventService.getEvents(query)valquery=DataQuery().filter(Event.name.equal("SAP Tech Ed 2015")).expand(Event.sessions).expand(Event.features)valevents=eventService.getEvents(query)
Multi-level expansion is also possible with the use of the expandWithQuery method. The following example shows how to retrieve an event with sessions returned inline. In addition, the track entity associated with each session instance will also be returned inline.
Specify the orderBy system query option to sort the returned set of entities.
// OData: /Events?$orderby=Country asc,Name asc// Sort by country in ascending order then by name in ascending order// Proxy ClassDataQueryquery=newDataQuery().orderBy(Event.country,SortOrder.ASCENDING).thenBy(Event.name,SortOrder.ASCENDING);List<Event>events=eventService.getEvents(query);// Dynamic APIDataQueryquery=newDataQuery().from(eventSet).orderBy(eventCountryProp,SortOrder.ASCENDING).thenBy(eventNameProp,SortOrder.ASCENDING);EntityValueListevents=dataService.executeQuery(query).getEntityList();
// OData: /Events?$orderby=Country asc,Name asc// Sort by country in ascending order then by name in ascending order// Proxy Classvalquery=DataQuery().orderBy(Event.country,SortOrder.ASCENDING).thenBy(Event.name,SortOrder.ASCENDING)valevents=eventService.getEvents(query)// Dynamic APIvalquery=DataQuery().from(eventSet).orderBy(eventCountryProp,SortOrder.ASCENDING).thenBy(eventNameProp,SortOrder.ASCENDING)valevents=dataService.executeQuery(query).entityList
In some cases, you may want to sort by a property of the entity associated with the navigation property. For example, you may want to list the sessions sorted by the name of their associated track. To do so, create a DataPath as a parameter for the orderBy method.
// OData: /Sessions?$filter=EventID eq 1000L&$expand=Track&$orderby=Track/Name// Proxy Class// Create the path Track/NameDataPathtrackNamePath=Session.track.path(Track.name);DataQuerysortQuery=newDataQuery().filter(Session.eventID.equal(1000L)).expand(Session.track).orderBy(trackNamePath);List<Session>sessions=eventService.getSessions(query);// Dynamic API// Create the path Track/NameDataPathtrackNamePath=sessionTrackProp.path(trackNameProp);DataQuerysortQuery=newDataQuery().from(setSession).filter(sessionEventIDProp.equal(1000L)).expand(sessionTrackProp).orderBy(trackNamePath);EntityValueListsessions=dataService.executeQuery(query).getEntityList();
// OData: /Sessions?$filter=EventID eq 1000L&$expand=Track&$orderby=Track/Name// Proxy Class// Create the path Track/NamevaltrackNamePath=Session.track.path(Track.name)valsortQuery=DataQuery().filter(Session.eventID.equal(1000L)).expand(Session.track).orderBy(trackNamePath)valsessions=eventService.getSessions(query)// Dynamic API// Create the path Track/NamevaltrackNamePath=sessionTrackProp.path(trackNameProp)valsortQuery=DataQuery().from(setSession).filter(sessionEventIDProp.equal(1000L)).expand(sessionTrackProp).orderBy(trackNamePath)valsessions=dataService.executeQuery(query).entityList
Use loadEntity to retrieve an entity using its entity key or readLink. If both are set, entity key takes precedence. If neither is set, an exception will be thrown.
When using proxy class, calling the default constructor will create a new event with each property set to its default value. Therefore, the following code will likely result in an "entity not found" exception due to the EventID being set to its default - zero.
Eventevent=newEvent();// Likely result in an entity not found exception because EventID is 0LeventService.loadEntity(event);// Create a new event without defaults for any propertyevent=newEvent(false);// An exception will be thrown because no identification is providedeventService.loadEntity(event);
varevent=Event()// Likely result in an entity not found exception because EventID is 0LeventService.loadEntity(event)// Create a new event without defaults for any propertyevent=Event(false)// An exception will be thrown because no identification is providedeventService.loadEntity(event)
You can use LoadEntity to lazy load properties, especially navigation properties for an entity that has already been retrieved. For example, when the list of features of an event is to be retrieved and the details of the event is to be shown, avoiding the use of the potentially expensive expand query option in the initial query.
// Proxy Class// An event is retrieved via a query, and its navigation properties are not selected
Eventevent=...;// Specify that the navigation properties, Features and Theme, are to be loadedDataQueryexpandQuery=newDataQuery().expand(Event.features,Event.theme);eventService.loadEntity(event,expandQuery);// Dynamic API// An event is retrieved via a query, and its navigation properties are not selectedEntityValueevent=...;// Specify that the navigation properties, Features and Theme, are to be loadedDataQueryexpandQuery=newDataQuery().expand(eventFeaturesProp,eventThemeProp);dataService.loadEntity(event,expandQuery);
// Proxy Class// An event is retrieved via a query, and its navigation properties are not selectedvalevent=...// Specify that the navigation properties, Features and Theme, are to be loadedvalexpandQuery=DataQuery().expand(Event.features,Event.theme)eventService.loadEntity(event,expandQuery)// Dynamic API// An event is retrieved via a query, and its navigation properties are not selectedvalevent=...// Specify that the navigation properties, Features and Theme, are to be loadedvalexpandQuery=DataQuery().expand(eventFeaturesProp,eventThemeProp)dataService.loadEntity(event,expandQuery)
While we can use loadEntity to retrieve properties of an entity, complex retrieval conditions can only be retrieved using loadProperty. In the example below, we are loading the top 50 sessions belonging to the event sorted by the name of the track associated with each session.
// Proxy Class// An event is retrieved via a queryEventevent=...;// Create a path to represent the name of the track associated with the sessionDataPathpath=Session.track.path(Track.name);// Define the retrieval criteria for sessions associated with this particular eventDataQuerysortQuery=newDataQuery().expand(Session.track).top(50).orderBy(path);eventService.loadProperty(Event.sessions,event,sortQuery);// Dynamic API// An event is retrieved via a queryEntityValueevent=...;// Create a path to represent the name of the track associated with the sessionDataPathpath=sessionTrackProp.path(trackNameProp);// Define the retrieval criteria for sessions associated with this particular eventDataQuerysortQuery=newDataQuery().expand(sessionTrackProp).top(50).orderBy(path);dataService.loadProperty(eventSessionsProp,event,sortQuery);
// Proxy Class// An event is retrieved via a queryvalevent=...// Create a path to represent the name of the track associated with the sessionvalpath=Session.track.path(Track.name)// Define the retrieval criteria for sessions associated with this particular eventvalsortQuery=DataQuery().expand(Session.track).top(50).orderBy(path)eventService.loadProperty(Event.sessions,event,sortQuery)// Dynamic API// An event is retrieved via a queryvalevent=...// Create a path to represent the name of the track associated with the sessionvalpath=sessionTrackProp.path(trackNameProp)// Define the retrieval criteria for sessions associated with this particular eventvalsortQuery=DataQuery().expand(sessionTrackProp).top(50).orderBy(path)dataService.loadProperty(eventSessionsProp,event,sortQuery)
If the EventID foreign key is exposed in Session entity type, we can do the same with a DataQuery against the Session collection.
OData provides two specific media metadata: media entity (think of a media entity as the metadata describing the binary data in the stream), and entity with stream properties. There are different strategies used to access online and offline media resources.
Offline will first try to get all entities of the entity set by performing a GET /FILE_DOWNLOADSET request.
And for each FILE_DOWNLOAD inside FILE_DOWNLOADSET, Offline will follow its media_src link to download media, if any:
GET FILE_DOWNLOADSET(1)/$value.
Offline will still need to call GET FILE_DOWNLOADSET(1) to get entity properties and metadata.
And then follow the media_src link to download FILE_DOWNLOADSET(1)/$value.
If, in that defining query, you set automaticallyRetrievesStreams to be true, it will also pull down the media stream for each media entity. At that point, you can access the stream locally by using the media stream's read link. If your automaticallyRetrievesStreams is set to false, it will still pull down the media entities (i.e. the metadata) but not the streams, so if you try to access the stream locally, it won't find anything. In that scenario, you would need to request() that the stream for individual media entities be downloaded by adding defining requests on a per-media entity basis, and then downloading/refreshing, at which point the stream can be accessed locally.
However, we suggest you set automaticallyRetrievesStreams to be true, as the back-end service may not implement GET requests for individual entities.
After the media entity is pulled down along with its media stream, use:
to load the media stream from the media entity. successHandler is called when the entity is successfully loaded and failureHandler is called if a failure occurred during loading.
If the entity is successfully loaded, its media stream can be decoded using BitmapFactory. Usually, this is implemented in successHandler. Here is a simple example:
DataService.downloadMediaAsync(mediaEntity,media->{Drawableimage=newBitmapDrawable(Resources,BitmapFactory.decodeByteArray(media,0,media.length));},error->{LOGGER.debug("Error encountered during load of media resource",error);
DataService.downloadMediaAsync(mediaEntity,{media->valimage=BitmapDrawable(Resources,BitmapFactory.decodeByteArray(media,0,media.size))},{error->LOGGER.debug("Error encountered during load of media resource",error)
In the example above, the media stream is decoded by BitmapFactory and then passed to BitmapDrawable to generate an image. Otherwise, an error message will be generated.
In this scenario, you don't have to download entity sets in advance. Data is downloaded by the application using the OData service base URL. So, the only concern is how to access the media resource.
Because the way to access media data of a media entity and an entity with stream properties is different, we first have to check each entity as to whether it is a media entity or an entity with stream properties. Suppose EntityValue is the entity that you want to handle, use
EntityValue.getEntityType().isMedia();
EntityValue.entityType.isMedia
to check if it is a media entity. Use the code below to check whether it has stream properties: