Using the OData API¶
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
Query¶
Collection/Entity Set¶
// 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 queried
List<Event> events = eventService.getEvents();
for (Event event: events) {
String eventName = event.getName();
// Get the start date time of the event
LocalDateTime startDateTime = 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 set
EntitySet eventSet = dataService.getEntitySet("Events");
// Get the type of the entity
EntityType eventType = eventSet.getEntityType();
// Use the from method of DataQuery to specify the entity set to retrieve from
DataQuery query = new DataQuery().from(eventSet);
EntityValueList events = dataService.executeQuery(query).getEntityList();
// Get event name value from result
Property eventNameProp = eventType.getProperty("Name");
Property eventStartDateTimeProp = eventType.getProperty("StartDateTime");
for (EntityValue event : events) {
String eventName = eventNameProp.getString(event);
LocalDateTime startDateTime =
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 queried
val events = eventService.events
for (event in events) {
val eventName = event.name
// Get the start date time of the event
val startDateTime = 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 set
val eventSet = dataService.getEntitySet("Events")
// Get the type of the entity
val eventType = eventSet.entityType
// Use the from method of DataQuery to specify the entity set to retrieve from
val query = DataQuery().from(eventSet)
val events = dataService.executeQuery(query).entityList
// Get event name value from result
val eventNameProp = eventType.getProperty("Name")
val eventStartDateTimeProp = eventType.getProperty("StartDateTime")
for (event in events) {
val eventName = eventNameProp.getString(event)
val startDateTime =
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.
Client-Driven Paging¶
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 getEvents
DataQuery query = new DataQuery().skip(100).top(50);
List<Event> events = eventService.getEvents(query);
// Dynamic API
DataQuery query = new DataQuery().from(eventSet).skip(100).top(50);
EntityValueList events = dataService.executeQuery(query).getEntityList();
// OData Query: /Events?$skip=100&$top=50
// Page size = 50, reading third page
// Proxy Class
// Use a query for getEvents
val query = DataQuery().skip(100).top(50)
val events = eventService.getEvents(query)
// Dynamic API
val query = DataQuery().from(eventSet).skip(100).top(50)
val events = 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
¶
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 against
DataQuery query = new DataQuery()
.from(EntitySets.events)
.skip(0)
.top(50)
.inlineCount();
QueryResult result = eventService.executeQuery(query);
long count = result.getInlineCount();
// a list of 50 events will be returned
List<Event> events = Event.list(result.getEntityList());
// Dynamic API
DataQuery query = new DataQuery()
.from(eventSet)
.skip(0)
.top(50)
.inlineCount();
QueryResult result = dataService.executeQuery(query);
long count = result.getInlineCount();
// a list of 50 events will be returned
EntityValueList events = 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 against
val query = DataQuery()
.from(EntitySets.events)
.skip(0)
.top(50)
.inlineCount()
val result = eventService.executeQuery(query)
val count = result.inlineCount
// a list of 50 events will be returned
val events = Event.list(result.entityList)
// Dynamic API
val query = DataQuery()
.from(eventSet)
.skip(0)
.top(50)
.inlineCount()
val result = dataService.executeQuery(query)
val count = result.inlineCount
// a list of 50 events will be returned
val events = result.entityList
Server-Driven Paging¶
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 Class
DataQuery pagedQuery = new DataQuery()
.from(EntitySets.events)
.page(10);
QueryResult result = eventService.executeQuery(pagedQuery);
List<Event> events = Event.list(result.getEntityList());
// Get the query based on the nextLink URI returned by the server
pagedQuery = result.getNextQuery();
// Ready to query the server again for the next page of entities
// Dynamic API
DataQuery pagedQuery = new DataQuery()
.from(eventSet)
.page(10);
QueryResult result = dataService.executeQuery(pagedQuery);
EntityValueList events = result.getEntityList();
// Get the query based on the nextLink URI returned by the server
pagedQuery = result.getNextQuery();
// Ready to query the server again for the next page of entities
// Proxy Class
var pagedQuery = DataQuery()
.from(EntitySets.events)
.page(10)
val result = eventService.executeQuery(pagedQuery)
val events = Event.list(result.entityList;
// Get the query based on the nextLink URI returned by the server
pagedQuery = result.nextQuery
// Ready to query the server again for the next page of entities
// Dynamic API
var pagedQuery = new DataQuery()
.from(eventSet)
.page(10)
val result = dataService.executeQuery(pagedQuery)
val events = result.entityList
// Get the query based on the nextLink URI returned by the server
pagedQuery = result.nextQuery
// Ready to query the server again for the next page of entities
Single Entity with Entity Key¶
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 Class
DataQuery query = new DataQuery().withKey(Event.key(1000L));
Event event = eventService.getEvent(query);
// Dynamic API
DataValue keyValue = 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)
EntityKey eventKey = new EntityKey().with("EventID", keyValue);
DataQuery query = new DataQuery().from(eventSet).withKey(eventKey);
EntityValue event = dataService.executeQuery(query).getRequiredEntity();
// OData Query: /Events(1000L)
// Proxy Class
val query = DataQuery().withKey(Event.key(1000L))
val event = eventService.getEvent(query)
// Dynamic API
val keyValue = 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)
val eventKey = EntityKey().with("EventID", keyValue)
val query = DataQuery().from(eventSet).withKey(eventKey)
val event = 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
.
EntityValue event = dataService.executeQuery(query).getOptionalEntity();
val event = dataService.executeQuery(query).optionalEntity
Filter¶
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 Class
DataQuery query = new DataQuery()
.filter(Event.name.equal("SAP Tech Ed 2015"));
List<Event> events = eventService.getEvents(query);
// Dynamic API
DataQuery query = new DataQuery()
.from(eventSet)
.filter(eventNameProp.equal("SAP Tech Ed 2015"));
EntityValueList events = dataService.executeQuery(query).getEntityList();
// OData Query: /Events?$filter=(Name eq 'SAP Tech Ed 2015')
// Proxy Class
val query = DataQuery().filter(Event.name.equal("SAP Tech Ed 2015"))
val events = eventService.getEvents(query)
// Dynamic API
val query = DataQuery()
.from(eventSet)
.filter(eventNameProp.equal("SAP Tech Ed 2015"))
val events = dataService.executeQuery(query).entityList
For complex filtering criteria, consider using the QueryOperator
within the filter.
// Find all sessions with registration >= 90%
// Proxy Class
DataQuery query = new DataQuery().filter(
QueryOperator.multiply(Session.participants, IntValue.of(100))
.divide(Session.maxParticipants)
.greaterEqual(IntValue.of(90)));
List<Session> sessions = eventService.getSessions(query);
// Dynamic API
DataQuery query = new DataQuery()
.from(sessionSet)
.filter(
QueryOperator.multiply(sessionParticipantsProp, IntValue.of(100))
.divide(sessionMaxParticipantsProp)
.greaterEqual(IntValue.of(90))
);
EntityValueList sessions = dataService.executeQuery(query).getEntityList();
// Find all sessions with registration >= 90%
// Proxy Class
val query = DataQuery().filter(
QueryOperator.multiply(Session.participants, IntValue.of(100))
.divide(Session.maxParticipants)
.greaterEqual(IntValue.of(90)))
val sessions = eventService.getSessions(query)
// Dynamic API
val query = DataQuery()
.from(sessionSet)
.filter(
QueryOperator.multiply(sessionParticipantsProp, IntValue.of(100))
.divide(sessionMaxParticipantsProp)
.greaterEqual(IntValue.of(90))
)
val sessions = dataService.executeQuery(query).entityList
Count¶
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.
// OData: /Events/$count
// Proxy Class
DataQuery query = new DataQuery().from(EntitySets.events).count();
long count = eventService.executeQuery(query).getCount();
// Dynamic API
DataQuery query = new DataQuery().from(eventSet).count();
long count = dataService.executeQuery(query).getCount();
// OData: /Events/$count&$filter=(Country eq 'USA')
// Proxy Class
DataQuery query = new DataQuery().from(EntitySets.events)
.filter(QueryOperator.equal(Event.country, StringValue.of('USA')))
.count();
long count = eventService.executeQuery(query).getCount();
// Dynamic API
DataQuery query = new DataQuery().from(eventSet)
.filter(QueryOperator.equal(eventCountryProp, StringValue.of('USA')))
.count();
long count = dataService.executeQuery(query).getCount();
// OData: /Events/$count
// Proxy Class
val query = DataQuery().from(EntitySets.events).count()
val count = eventService.executeQuery(query).count
// Dynamic API
val query = DataQuery().from(eventSet).count()
val count = dataService.executeQuery(query).count
// OData: /Events/$count&$filter=(Country eq 'USA')
// Proxy Class
val query = DataQuery().from(EntitySets.events)
.filter(QueryOperator.equal(Event.country, StringValue.of('USA')))
.count()
val count = eventService.executeQuery(query).count
// Dynamic API
val query = DataQuery().from(eventSet)
.filter(QueryOperator.equal(eventCountryProp, StringValue.of('USA')))
.count()
val count = dataService.executeQuery(query).count
Expand¶
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 Class
DataQuery query = new DataQuery()
.filter(Event.name.equal("SAP Tech Ed 2015"))
.expand(Event.sessions);
List<Event> events = eventService.getEvents(query);
// Dynamic API
Property eventSessionsProp = eventType.getProperty("Sessions");
DataQuery query = new DataQuery().from(eventSet)
.filter(eventNameProp.equal("SAP Tech Ed 2015"))
.expand(eventSessionsProp);
EntityValueList events = dataService.executeQuery(query).getEntityList();
// OData: /Events?$filter=(Name eq 'SAP Tech Ed 2015')&$expand=Sessions
// Proxy Class
val query = DataQuery()
.filter(Event.name.equal("SAP Tech Ed 2015"))
.expand(Event.sessions)
val events = eventService.getEvents(query)
// Dynamic API
val eventSessionsProp = eventType.getProperty("Sessions")
val query = DataQuery().from(eventSet)
.filter(eventNameProp.equal("SAP Tech Ed 2015"))
.expand(eventSessionsProp)
val events = 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 Class
DataQuery query = new DataQuery()
.filter(Event.name.equal("SAP Tech Ed 2015"))
.expand(Event.sessions, Event.features);
List<Event> events = eventService.getEvents(query);
DataQuery query = new DataQuery()
.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 Class
val query = DataQuery()
.filter(Event.name.equal("SAP Tech Ed 2015"))
.expand(Event.sessions, Event.features)
val events = eventService.getEvents(query)
val query = DataQuery()
.filter(Event.name.equal("SAP Tech Ed 2015"))
.expand(Event.sessions)
.expand(Event.features)
val events = 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.
// OData: /Events(1000L)?$expand=Sessions/Track
// Proxy Class
DataQuery query = new DataQuery()
.withKey(Event.key(1000L))
.expandWithQuery(
Event.sessions,
new DataQuery().expandWithQuery(Session.track, new DataQuery())
);
List<Event> events = eventService.getEvents(query);
// Dynamic API
EntityKey eventKey = new EntityKey().with("EventID", LongValue.of(1000L));
DataQuery query = new DataQuery()
.withKey(eventKey)
.expandWithQuery(
eventSessionsProp,
new DataQuery().expandWithQuery(sessionTrackProp, new DataQuery())
);
List<Event> events = eventService.getEvents(query);
// OData: /Events(1000L)?$expand=Sessions/Track
// Proxy Class
val query = DataQuery()
.withKey(Event.key(1000L))
.expandWithQuery(
Event.sessions,
DataQuery().expandWithQuery(Session.track, DataQuery())
)
val events = eventService.getEvents(query)
// Dynamic API
val eventKey = EntityKey().with("EventID", LongValue.of(1000L))
val query = DataQuery()
.withKey(eventKey)
.expandWithQuery(
eventSessionsProp,
DataQuery().expandWithQuery(sessionTrackProp, DataQuery())
)
val events = eventService.getEvents(query)
orderBy
¶
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 Class
DataQuery query = new DataQuery()
.orderBy(Event.country, SortOrder.ASCENDING)
.thenBy(Event.name, SortOrder.ASCENDING);
List<Event> events = eventService.getEvents(query);
// Dynamic API
DataQuery query = new DataQuery()
.from(eventSet)
.orderBy(eventCountryProp, SortOrder.ASCENDING)
.thenBy(eventNameProp, SortOrder.ASCENDING);
EntityValueList events = dataService.executeQuery(query).getEntityList();
// OData: /Events?$orderby=Country asc,Name asc
// Sort by country in ascending order then by name in ascending order
// Proxy Class
val query = DataQuery()
.orderBy(Event.country, SortOrder.ASCENDING)
.thenBy(Event.name, SortOrder.ASCENDING)
val events = eventService.getEvents(query)
// Dynamic API
val query = DataQuery()
.from(eventSet)
.orderBy(eventCountryProp, SortOrder.ASCENDING)
.thenBy(eventNameProp, SortOrder.ASCENDING)
val events = 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/Name
DataPath trackNamePath = Session.track.path(Track.name);
DataQuery sortQuery = new DataQuery()
.filter(Session.eventID.equal(1000L))
.expand(Session.track)
.orderBy(trackNamePath);
List<Session> sessions = eventService.getSessions(query);
// Dynamic API
// Create the path Track/Name
DataPath trackNamePath = sessionTrackProp.path(trackNameProp);
DataQuery sortQuery = new DataQuery().from(setSession)
.filter(sessionEventIDProp.equal(1000L))
.expand(sessionTrackProp)
.orderBy(trackNamePath);
EntityValueList sessions = dataService.executeQuery(query).getEntityList();
// OData: /Sessions?$filter=(EventID eq 1000L)&$expand=Track&$orderby=Track/Name
// Proxy Class
// Create the path Track/Name
val trackNamePath = Session.track.path(Track.name)
val sortQuery = DataQuery()
.filter(Session.eventID.equal(1000L))
.expand(Session.track)
.orderBy(trackNamePath)
val sessions = eventService.getSessions(query)
// Dynamic API
// Create the path Track/Name
val trackNamePath = sessionTrackProp.path(trackNameProp)
val sortQuery = DataQuery().from(setSession)
.filter(sessionEventIDProp.equal(1000L))
.expand(sessionTrackProp)
.orderBy(trackNamePath)
val sessions = dataService.executeQuery(query).entityList
Select¶
Use the select
system query option to specify a subset of the structural properties to be returned with the query.
// OData: /Events?$select=Name,EventID,Country
// Proxy Class
DataQuery query = new DataQuery().select(Event.name, Event.eventID, Event.country);
List<Event> events = eventService.getEvents(query);
// Dynamic API
DataQuery query = new DataQuery()
.from(eventSet)
.select(eventNameProp, eventEventIDProp, eventCountryProp);
EntityValueList events = dataService.executeQuery(query).getEntityList();
// OData: /Events?$select=Name,EventID,Country
// Proxy Class
val query = DataQuery().select(Event.name, Event.eventID, Event.country)
val events = eventService.getEvents(query)
// Dynamic API
val query = DataQuery()
.from(eventSet)
.select(eventNameProp, eventEventIDProp, eventCountryProp)
val events = dataService.executeQuery(query).entityList
Exception will be thrown if non-selected properties are accessed.
loadEntity
¶
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.
Using Entity Key
// Proxy Class
Event event = new Event();
event.setEventID(1000L);
eventService.loadEntity(event);
// Dynamic API
EntityValue event = EntityValue.ofType(eventType);
eventEventIDProp.setLong(event, 1000L);
dataService.loadEntity(event);
// Proxy Class
val event = Event()
event.eventID = 1000L
eventService.loadEntity(event)
// Dynamic API
val event = EntityValue.ofType(eventType)
eventEventIDProp.setLong(event, 1000L)
dataService.loadEntity(event)
Using ReadLink
// Proxy Class
Event event = new Event();
event.setReadLink("...");
eventService.loadEntity(event);
// Dynamic API
EntityValue event = EntityValue.ofType(eventType);
event.setReadLink("...");
dataService.loadEntity(event);
// Proxy Class
val event = Event()
event.readLink = "..."
eventService.loadEntity(event)
// Dynamic API
val event = EntityValue.ofType(eventType)
event.readLink = "..."
dataService.loadEntity(event)
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.
Event event = new Event();
// Likely result in an entity not found exception because EventID is 0L
eventService.loadEntity(event);
// Create a new event without defaults for any property
event = new Event(false);
// An exception will be thrown because no identification is provided
eventService.loadEntity(event);
var event = Event()
// Likely result in an entity not found exception because EventID is 0L
eventService.loadEntity(event)
// Create a new event without defaults for any property
event = Event(false)
// An exception will be thrown because no identification is provided
eventService.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
Event event = ...;
// Specify that the navigation properties, Features and Theme, are to be loaded
DataQuery expandQuery = new 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 selected
EntityValue event = ...;
// Specify that the navigation properties, Features and Theme, are to be loaded
DataQuery expandQuery = new DataQuery().expand(eventFeaturesProp, eventThemeProp);
dataService.loadEntity(event, expandQuery);
// Proxy Class
// An event is retrieved via a query, and its navigation properties are not selected
val event = ...
// Specify that the navigation properties, Features and Theme, are to be loaded
val expandQuery = 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 selected
val event = ...
// Specify that the navigation properties, Features and Theme, are to be loaded
val expandQuery = DataQuery().expand(eventFeaturesProp, eventThemeProp)
dataService.loadEntity(event, expandQuery)
loadProperty
¶
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 query
Event event = ...;
// Create a path to represent the name of the track associated with the session
DataPath path = Session.track.path(Track.name);
// Define the retrieval criteria for sessions associated with this particular event
DataQuery sortQuery = new DataQuery().expand(Session.track).top(50).orderBy(path);
eventService.loadProperty(Event.sessions, event, sortQuery);
// Dynamic API
// An event is retrieved via a query
EntityValue event = ...;
// Create a path to represent the name of the track associated with the session
DataPath path = sessionTrackProp.path(trackNameProp);
// Define the retrieval criteria for sessions associated with this particular event
DataQuery sortQuery = new DataQuery().expand(sessionTrackProp).top(50).orderBy(path);
dataService.loadProperty(eventSessionsProp, event, sortQuery);
// Proxy Class
// An event is retrieved via a query
val event = ...
// Create a path to represent the name of the track associated with the session
val path = Session.track.path(Track.name)
// Define the retrieval criteria for sessions associated with this particular event
val sortQuery = DataQuery().expand(Session.track).top(50).orderBy(path)
eventService.loadProperty(Event.sessions, event, sortQuery)
// Dynamic API
// An event is retrieved via a query
val event = ...
// Create a path to represent the name of the track associated with the session
val path = sessionTrackProp.path(trackNameProp)
// Define the retrieval criteria for sessions associated with this particular event
val sortQuery = 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.
// Proxy Class
DataPath path = Session.track.path(Track.name);
DataQuery query = new DataQuery().filter(Session.eventID.equal(1000L))
.expand(Session.track)
.top(50)
.orderBy(path);
List<Session> sessions = eventService.getSessions(query);
// Dynamic API
DataPath path = sessionTrackProp.path(trackNameProp);
DataQuery query = new DataQuery().filter(sessionEventIDProp.equal(1000L))
.expand(sessionTrackProp)
.top(50)
.orderBy(path);
EntityValueList sessions = dataService.executeQuery(query).getEntityList();
// Proxy Class
val path = Session.track.path(Track.name)
val query = DataQuery().filter(Session.eventID.equal(1000L))
.expand(Session.track)
.top(50)
.orderBy(path)
val sessions = eventService.getSessions(query)
// Dynamic API
val path = sessionTrackProp.path(trackNameProp)
val query = new DataQuery().filter(sessionEventIDProp.equal(1000L))
.expand(sessionTrackProp)
.top(50)
.orderBy(path)
val sessions = dataService.executeQuery(query).entityList
Media Download¶
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¶
Offline only supports OData version 2 currently, and version 2 doesn’t support stream properties.
-
Suppose
FILE_DOWNLOADSET
is anentityset
, when you use something like://OfflineDataDefiningQuery(String name, String query, boolean automaticallyRetrievesStreams) new OfflineDataDefiningQuery("FILE_DOWNLOADSET", "FILE_DOWNLOADSET", true);
//OfflineDataDefiningQuery(String name, String query, boolean automaticallyRetrievesStreams) OfflineDataDefiningQuery("FILE_DOWNLOADSET", "FILE_DOWNLOADSET", true)
Offline will first try to get all entities of the entity set by performing a
GET /FILE_DOWNLOADSET
request. And for eachFILE_DOWNLOAD
insideFILE_DOWNLOADSET
, Offline will follow itsmedia_src
link to download media, if any:GET FILE_DOWNLOADSET(1)/$value
.If you want to download an individual stream:
//OfflineDataDefiningQuery(String name, String query, boolean automaticallyRetrievesStreams) new OfflineDataDefiningQuery("FILE_DOWNLOADSET", "FILE_DOWNLOADSET(1)", true);
//OfflineDataDefiningQuery(String name, String query, boolean automaticallyRetrievesStreams) OfflineDataDefiningQuery("FILE_DOWNLOADSET", "FILE_DOWNLOADSET(1)", true)
Offline will still need to call
GET FILE_DOWNLOADSET(1)
to get entity properties and metadata. And then follow themedia_src
link to downloadFILE_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 yourautomaticallyRetrievesStreams
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 torequest()
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.Note
However, we suggest you set
automaticallyRetrievesStreams
to be true, as the back-end service may not implementGET
requests for individual entities. -
After the media entity is pulled down along with its media stream, use:
DataService.downloadMediaAsync(entity, successHandler, failureHandler);
DataService.downloadMediaAsync(entity, successHandler, failureHandler)
to load the media stream from the media entity.
successHandler
is called when the entity is successfully loaded andfailureHandler
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 insuccessHandler
. Here is a simple example:DataService.downloadMediaAsync(mediaEntity, media -> { Drawable image = new BitmapDrawable(Resources, BitmapFactory.decodeByteArray(media, 0, media.length)); }, error -> { LOGGER.debug("Error encountered during load of media resource", error); });
DataService.downloadMediaAsync(mediaEntity, { media -> val image = 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 toBitmapDrawable
to generate an image. Otherwise, an error message will be generated.
Additional information can be found: Offline OData Media Resources
Online¶
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, useEntityValue.getEntityType().isMedia();
EntityValue.entityType.isMedia
to check if it is a media entity. Use the code below to check whether it has stream properties:
EntityValue.getEntityType().getStreamProperties().length() > 0
EntityValue.entityType.streamProperties.length() > 0
If either of the above checks returns True, we can move to next step.
-
Unlike the Offline scenario, we need the media resource URL as well as the OData base URL to pull down the media stream for each entity.
-
Media entity
For media entities, use the following to get the media resource URL.
String mediaLinkURL = EntityValue.getMediaStream().getReadLink();
val mediaLinkURL = EntityValue.mediaStream.readLink
-
Entity with stream properties
An entity could have zero or more stream properties. In that case, you may have to iterate the
PropertyList
. The following example demonstrates how to get (the first) one stream property's URL.PropertyList ResourceProperties = EntityValue.getEntityType().getStreamProperties(); String mediaLinkURL = ResourceProperties.first().getStreamLink(EntityValue).getReadLink();
val ResourceProperties = EntityValue.entityType.streamProperties() val mediaLinkURL = ResourceProperties.first().getStreamLink(EntityValue).readLink
Note
Media resource URL could be null.
After obtaining the media resource URL, pass it, combined with OData base URL, to some kind of
RequestManager
to load. In the following example,Glide.with()
is aRequestManager
.Glide.with(currentActivity).load(ODATA_URL + mediaLinkURL);
Glide.with(currentActivity).load(ODATA_URL + mediaLinkURL)
-
Custom Query¶
Developers who are familiar with the OData protocol can specify the OData requests directly. However, the correctness of the requests and using the QueryResult
appropriately are also the responsibility of the developers. In addition, the query options described previously e.g. filter, expand, etc., are ignored for a custom query.
// Proxy Class
DataQuery query = new DataQuery()
.withKey(Event.key(1000L))
.expand(Event.features);
// Load the event and associated features specified by the key
Event event = eventService.getEvent(query);
// Get the readLink for the event returned
String readLink = event.getReadLink();
// Create custom query to expand on features associated with event
// This query does the same as the previously query
query = new DataQuery()
.withURL(readLink + "?$expand=" + Event.features.getName());
event = eventService.getEvent(query);
// Dynamic API
DataValue keyValue = LongValue.of(1000L);
EntityKey eventKey = new EntityKey().with("EventID", keyValue);
DataQuery query = new DataQuery().from(eventSet)
.withKey(eventKey)
.expand(eventFeaturesProp);
// Load the event and associated features specified by the key
EntityValue event = dataService.executeQuery(query).getRequiredEntity();
// Get the readLink for the event returned
String readLink = event.getReadLink();
// Create custom query to expand on features associated with event
// This query does the same as the previously query
query = new DataQuery()
.withURL(readLink + "?$expand=" + eventFeaturesProp.getName());
event = dataService.executeQuery(query).getRequiredEntity();
// Proxy Class
var query = DataQuery().withKey(Event.key(1000L)).expand(Event.features)
// Load the event and associated features specified by the key
var event = eventService.getEvent(query)
// Get the readLink for the event returned
val readLink = event.readLink
// Create custom query to expand on features associated with event
// This query does the same as the previously query
query = DataQuery()
.withURL(readLink + "?$expand=" + Event.features.getName())
event = eventService.getEvent(query)
// Dynamic API
val keyValue = LongValue.of(1000L)
val eventKey = EntityKey().with("EventID", keyValue)
var query = DataQuery().from(eventSet)
.withKey(eventKey)
.expand(eventFeaturesProp)
// Load the event and associated features specified by the key
val event = dataService.executeQuery(query).requiredEntity
// Get the readLink for the event returned
val readLink = event.readLink
// Create custom query to expand on features associated with event
// This query does the same as the previously query
query = new DataQuery()
.withURL(readLink + "?$expand=" + eventFeaturesProp.getName())
event = dataService.executeQuery(query).requiredEntity
CUD Operations¶
The required parameter is an entity of the entity type operated on. Update is usually performed on an entity retrieved from the provider. However, all three operations can use a new entity as well.
CreateEntity
¶
// Proxy Class
Event event = new Event();
// Using type specific setters to set the property value
event.setName("A New Event");
event.setType(("Public");
event.setDescription("Another event...");
// Set start date time to be a local date time: 7/4/2018 08:15:00
event.setStartDateTime(LocalDateTime.of(2018, 7, 4, 8, 15, 0));
...
eventService.createEntity(event);
// Dynamic API
// Create an EntityValue of the entity type of Event
EntityValue event = EntityValue.ofType(eventType);
eventNameProp.setString(event, "A New Event");
eventTypeProp.setString(event, "Public");
eventDescriptionProp.setString(event, "Another event...");
// Set start date time to be a local date time: 7/4/2018 08:15:00
eventStartDateTimeProp.setValue(event, LocalDateTime.of(2018, 7, 4, 8, 15, 0));
...
dataService.createEntity(event);
// Proxy Class
val event = Event()
// Using type specific setters to set the property value
event.name = "A New Event"
event.type = "Public"
event.description = "Another event..."
// Set start date time to be a local date time: 7/4/2018 08:15:00
event.startDateTime = LocalDateTime.of(2018, 7, 4, 8, 15, 0)
...
eventService.createEntity(event)
// Dynamic API
// Create an EntityValue of the entity type of Event
val event = EntityValue.ofType(eventType)
eventNameProp.setString(event, "A New Event")
eventTypeProp.setString(event, "Public")
eventDescriptionProp.setString(event, "Another event...")
// Set start date time to be a local date time: 7/4/2018 08:15:00
eventStartDateTimeProp.setValue(event, LocalDateTime.of(2018, 7, 4, 8, 15, 0))
...
dataService.createEntity(event)
UpdateEntity
¶
The typical flow for update is:
- Retrieve the entity
- Examine/Display properties to be changed
-
Set properties to their new values
-
Invoke the
updateEntity
method, passing in the entity
By default, the library uses merge and only sends changed properties to the OData service.
// Proxy Class
// Read Event entity from provider
Event event = …;
event.setCountry("Canada");
eventService.updateEntity(event);
// Dynamic API
// Read event entity from provider
EntityValue event = …;
eventCountryProp.setString(event, "Canada");
dataService.updateEntity(event);
// Proxy Class
// Read Event entity from provider
val event = …
event.country = "Canada"
eventService.updateEntity(event)
// Dynamic API
// Read event entity from provider
val event = …
eventCountryProp.setString(event, "Canada")
dataService.updateEntity(event)
If there is no need to review existing values, change the properties as follows:
- Construct a new entity
- Set either primary key or
editLink
- Invoke the
rememberOld
method, which saves the current value of all properties - Set properties to their new values
- Invoke the
UpdateEntity
method, passing in the new entity
// Proxy Class
Event event = new Event();
// Set the entity key of the event we want to update
event.setEventID(1000L);
// sets old value to current value. Note that we need old and new to determine
// what has changed
event.rememberOld();
// update the property
event.setCountry("Canada");
eventService.updateEntity(event);
// Dynamic API
EntityValue event = EntityValue.ofType(eventType);
// Set the entity key of the event we want to update
eventEventIDProp.setLong(event, 1000L);
// sets old value to current value. Note that we need old and new to determine
// what has changed
event.rememberOld();
eventCountryProp.setString(event, "Canada");
dataService.updateEntity(event);
// Proxy Class
val event = Event()
// Set the entity key of the event we want to update
event.eventID = 1000L
// sets old value to current value. Note that we need old and new to determine
// what has changed
event.rememberOld()
// update the property
event.country = "Canada"
eventService.updateEntity(event)
// Dynamic API
val event = EntityValue.ofType(eventType)
// Set the entity key of the event we want to update
eventEventIDProp.setLong(event, 1000L)
// sets old value to current value. Note that we need old and new to determine
// what has changed
event.rememberOld()
eventCountryProp.setString(event, "Canada")
dataService.updateEntity(event)
If you want to use the replace semantic instead of merge for a particular update, pass RequestOptions
as the second parameter. Because the entire entity is replaced, be sure to first read the existing entity before changing any properties.
// Proxy Class
RequestOptions updateOptions = new RequestOptions();
updateOptions.setUpdateMode(UpdateMode.REPLACE);
// Important: Read the entity in first
Event event = ...;
// Change properties...
event.setName("Test Event");
// Use a variant of updateEntity that supports the passing in of
// http headers and request options
eventService.updateEntity(event, new HttpHeaders(), updateOptions);
// Proxy Class
val updateOptions = RequestOptions()
updateOptions.updateMode = UpdateMode.REPLACE
// Important: Read the entity in first
val event = ...
// Change properties...
event.name = "Test Event"
// Use a variant of updateEntity that supports the passing in of
// http headers and request options
eventService.updateEntity(event, new HttpHeaders(), updateOptions)
If the OData service only supports HTTP PUT
, turn off the merge option for the OData online/offline provider:
provider.getServiceOptions().setSupportsPatch(false);
provider.getServiceOptions().setSupportsPatch(false)
deleteEntity
¶
// Proxy Class
Event event = new Event();
// Set the entity key of the event we want to delete
event.setEventID(1000L);
eventService.deleteEntity(event);
// Dynamic API
EntityValue entityValue = EntityValue.ofType(eventType);
// Set the entity key of the event we want to delete
eventEventIDProp.setLong(entityValue, 1000L);
dataService.deleteEntity(entityValue);
// Proxy Class
val event = Event()
// Set the entity key of the event we want to delete
event.eventID = 1000L
eventService.deleteEntity(event)
// Dynamic API
val entityValue = EntityValue.ofType(eventType)
// Set the entity key of the event we want to delete
eventEventID.setLong(entityValue, 1000L)
dataService.deleteEntity(entityValue)
If you want to delete multiple entities, you can use the deleteByQuery
method. Because there is no OData equivalent, Dynamic API first executes the query to retrieve the list of entities to be deleted, and then uses batch processing and ChangeSet
to apply all the deletes in one request.
Note
Batch processing and ChangeSet
are described in later sections.
// Proxy Class
DataQuery query = new DataQuery()
.from(eventSet)
.filter(Event.country.equal("USA"));
eventService.deleteByQuery(query);
// Dynamic API
DataQuery query = new DataQuery()
.from(eventSet)
.filter(eventCountryProp.equal("USA"));
dataService.deleteByQuery(query);
// Proxy Class
val query = DataQuery()
.from(eventSet)
.filter(Event.country.equal("USA"))
eventService.deleteByQuery(query)
// Dynamic API
val query = DataQuery()
.from(eventSet)
.filter(eventCountryProp.equal("USA"))
dataService.deleteByQuery(query)
Relations Between Entities¶
There are multiple ways to establish relationships between entities in OData:
- Establish or maintain a relationship between existing entities
- Create a new entity and establish a relationship with an existing entity
- Create new entities and establish relationships between them
Note
In some data models, the foreign keys are exposed as a property, so you can establish relations by setting the property to the entity key value of the related entity. However, with many-to-many relations, we rely on one of the ways described below.
Existing Entities¶
Use link commands to create, delete, or modify relations between existing entities via the navigation property. Note that for offline, bidirectional many-to-many relations require multiple actions. Please refer to the offline documentation for additional information.
createLink
¶
Establish a relation between two entities. The navigation property can have a to-one or to-many cardinality .
// Proxy Class
// Load the event to which we want to add a feature association
Event event = …;
// Load the feature that we want the event to be associated with
Feature feature = …;
// Link feature to event
// Note Event.features is the navigation property from event to features
eventService.createLink(event, Event.features, feature);
// Dynamic API
// Load the event to which we want to add a feature association
EntityValue event = …;
// Load the feature that we want the event to be associated with
EntityValue feature = …;
// Link feature to event
dataService.createLink(event, eventFeaturesProp, feature);
// Note that there is no difference between proxy class and Dynamic API in Kotlin
// as it is able to determine the data type automatically
// Proxy Class
// Load the event to which we want to add a feature association
val event = …
// Load the feature that we want the event to be associated with
val feature = …
// Link feature to event
// Note Event.features is the navigation property from event to features
eventService.createLink(event, Event.features, feature)
// Dynamic API
// Load the event to which we want to add a feature association
val event = …
// Load the feature that we want the event to be associated with
val feature = …
// Link feature to event
dataService.createLink(event, eventFeaturesProp, feature)
deleteLink
¶
Delete a relation between two entities. The navigation property can have to-one or to-many cardinality.
// Proxy Class
eventService.deleteLink(event, Event.features, feature);
// Dynamic API
dataService.deleteLink(event, eventFeaturesProp, feature);
// Proxy Class
eventService.deleteLink(event, Event.features, feature)
// Dynamic API
dataService.deleteLink(event, eventFeaturesProp, feature)
If one of the entities is deleted via deleteEntity
, any existing relations with other entities will be deleted as well.
updateLink
¶
This method can only be used for navigation properties having a cardinality of one. Using it on a to-many navigation property will result in an exception. It is always possible to use deleteLink
and createLink
to accomplish the same. In the example below, each event can be associated with no or one theme.
// Proxy Class
eventService.updateLink(event, Event.Theme, anotherTheme);
// Dynamic API
dataService.updateLink(event, eventThemeProp, anotherTheme);
// Proxy Class
eventService.updateLink(event, Event.Theme, anotherTheme)
// Dynamic API
dataService.updateLink(event, eventThemeProp, anotherTheme)
Create New Entity and Associate with an Existing Entity¶
There are two ways to create a new entity and associate it with an existing entity in OData. While they are functionally the same, an OData service may support only one.
bindEntity
¶
Creates the new entity against its own entity set. The new entity is bound to an existing entity, with which the relationship is to be established.
// OData: POST /Sessions
// Proxy Class
// Create a session
Session session = new Session(false);
// set properties...
// Locate the event to associate with
Event event = …;
// Bind the new session to the specified event
session.bindEntity(event, Session.event);
eventService.createEntity(session);
// Dynamic API
// Create a session
EntityValue session = EntityValue.ofType(sessionType);
// Set properties...
// Locate event to be associated with
EntityValue event = ...;
// Bind the new session to the specified event
session.bindEntity(event, sessionEventProp);
dataService.createEntity(session);
// OData: POST /Sessions
// Proxy Class
// Create a session
val session = Session(false)
// set properties...
// Locate the event to associate with
val event = …
// Bind the new session to the specified event
session.bindEntity(event, Session.event)
eventService.createEntity(session)
// Dynamic API
// Create a session
val session = EntityValue.ofType(sessionType)
// Set properties...
// Locate event to be associated with
val event = ...
// Bind the new session to the specified event
session.bindEntity(event, sessionEventProp)
dataService.createEntity(session)
Create Through Related Entity¶
createRelatedEntity
creates the new entity against the navigation property of the entity with which the relationship is to be established. The difference between this and bindEntity
is that this operation is against the entity set of the entity to be associated with.
// OData: POST /Events(1000L)/Sessions
// Proxy Class
// Create a session
Session session = new Session(false);
// set properties...
// Locate the event to associate with
Event event = ...;
// Bind the new session to the specified event
eventService.createRelatedEntity(session, event, Event.sessions);
// Dynamic API
// Create a session
EntityValue session = EntityValue.ofType(sessionType);
// Set properties...
// Locate the event to associate with
EntityValue event = ...;
// Bind the new session to the specified event
dataService.createRelatedEntity(session, event, eventSessionsProp);
// OData: POST /Events(1000L)/Sessions
// Proxy Class
// Create a session
val session = Session(false)
// set properties...
// Locate the event to associate with
Event event = ...
// Bind the new session to the specified event
eventService.createRelatedEntity(session, event, Event.sessions)
// Dynamic API
// Create a session
val session = EntityValue.ofType(sessionType)
// Set properties...
// Locate the event to associate with
val event = ...
// Bind the new session to the specified event
dataService.createRelatedEntity(session, event, eventSessionsProp)
Create New Entities with Associations¶
You can create new entities with associations using a deep insert or batch processing. Batch processing is discussed in a subsequent section.
Deep Insert¶
Deep insert refers to the creation of an entity that includes related entities. The operation is atomic and either all entities are created, or none is. It is most often used to create hierarchical data, such as a sales order with line items, but can also be used for non-containment relations.
// Proxy Class
// Create new event and set its properties
Event event = ...;
// Create a session and set its properties
Session session1 = ...;
// Create a second session and set its properties
Session session2 = ...;
// Create Session List
List<Session> sessions = new ArrayList<>();
sessions.add(session1);
sessions.add(session2);
event.setSessions(sessions);
// Create new event with associated sessions
eventService.createEntity(event);
// Dynamic API
// Create new event and set its properties
EntityValue event = EntityValue.ofType(eventType);
...
// Create a session and set its properties
EntityValue session1 = EntityValue.ofType(sessionType);
...
// Create a second session and set its properties
EntityValue session2 = EntityValue.ofType(sessionType);
...
// Create Session List
EntityValueList sessions = new EntityValueList();
sessions.add(session1);
sessions.add(session2);
eventSessionsProps.setEntityList(event, sessions);
// Create new event with associated sessions
dataService.createEntity(event);
// Proxy Class
// Create new event and set its properties
val event = ...
// Create a session and set its properties
val session1 = ...
// Create a second session and set its properties
val session2 =...
// Create Session List
val sessions = ArrayList<Session>()
sessions.add(session1)
sessions.add(session2)
event.sessions = sessions
// Create new event with associated sessions
eventService.createEntity(event)
// Dynamic API
// Create new event and set its properties
val event = EntityValue.ofType(eventType)
...
// Create a session and set its properties
val session1 = EntityValue.ofType(sessionType)
...
// Create a second session and set its properties
val session2 = EntityValue.ofType(sessionType)
...
// Create Session List
val sessions = EntityValueList()
sessions.add(session1)
sessions.add(session2)
eventSessionsProps.setEntityList(event, sessions)
// Create new event with associated sessions
dataService.createEntity(event)
Service Operations¶
The generated service class provides a corresponding method for each service operation in the metadata document. The preferred way is to invoke service operations via these generated methods. With Dynamic API, use executeQuery
and DataMethodCall
to invoke service operations.
// Proxy Class
Long count = eventService.getSessionAttendeesCount(session.getSessionID());
// Dynamic API
EntityValue session = ...;
DataMethod dataMethod = dataService.getDataMethod("getSessionAttendeesCount");
Long sessionID = sessionSessionIDProp.getLong(session);
DataMethodCall methodCall = DataMethodCall.apply(
dataMethod,
new ParameterList(1).with("SessionInstanceID", LongValue.of(sessionInstanceID)));
DataQuery query = new DataQuery();
query.setMethodCall(methodCall);
// Use DataService executeQuery
Long count = LongValue.toNullable(dataService.executeQuery(query).getResult());
// Proxy Class
val count = eventService.getSessionAttendeesCount(session.getSessionID())
// Dynamic API
val session = ...
val dataMethod = dataService.getDataMethod("getSessionAttendeesCount")
val sessionID = sessionSessionIDProp.getLong(session)
val methodCall = DataMethodCall.apply(
dataMethod,
ParameterList(1).with("SessionInstanceID", LongValue.of(sessionInstanceID)))
val query = DataQuery()
query.setMethodCall(methodCall)
// Use DataService executeQuery
val count = LongValue.toNullable(dataService.executeQuery(query).result)
If the service operation returns a list of entities, you can use getEntityList
instead of getResult
.
Alternately, use the convenience method executeMethod
to simplify invocation.
// Use DataService executeMethod
DataMethod dataMethod = dataService.getDataMethod("getSessionAttendeesCount");
Long count = LongValue.toNullable(
dataService.executeMethod(
dataMethod,
new ParameterList(1).with("SessionInstanceID", LongValue.of(sessionInstanceID))
));
// Use DataService executeMethod
val dataMethod = dataService.getDataMethod("getSessionAttendeesCount")
val count = LongValue.toNullable(
dataService.executeMethod(
dataMethod,
ParameterList(1).with("SessionInstanceID", LongValue.of(sessionInstanceID))
))
Batch Processing¶
OData allows you to submit an ordered series of query operations and/or change sets. To leverage this capability, use the ChangeSet
and RequestBatch
classes. Note that all requests within a batch are guaranteed to be executed in the order they are listed. The usage of these classes is the same regardless of whether you use proxy class or Dynamic API, although generated artifacts can simplify the construction of data queries and modification requests.
The status code returned from a batch is always 202. No exception is thrown by processBatch
should any of the queries or change sets within it fail. It is the developer's responsibility to check the status of queries or change sets via getStatus
.
Data Query¶
The data query added to the batch must specify the entity set that it is querying against.
// Prepare queries
DataQuery query1 = new DataQuery().from(sessionSet);
DataQuery query2 = new DataQuery().from(eventSet).withKey(Event.key(999L));
DataQuery query3 = new DataQuery().from(eventSet).orderBy(Event.name);
// Create a new batch request
RequestBatch batch = new RequestBatch();
// Add the data queries to the batch
batch.addQuery(query1);
batch.addQuery(query2);
batch.addQuery(query3);
// Execute batch
dataService.processBatch(batch);
// Get results for each data query
QueryResult result1 = batch.getQueryResult(query1);
QueryResult result2 = batch.getQueryResult(query2);
QueryResult result3 = batch.getQueryResult(query3);
if (result1.getStatus() == 200) {
// Convert returned EntityValueList to a strongly typed List
List<Session> sessions = Session.list(result1.getEntityList());
}
if (result2.getStatus() == 200) {
// Convert returned EntityValueList to a strongly typed List
List<Event> events = Event.list(result2.getEntityList());
}
if (result3.getStatus() == 200) {
// Convert returned EntityValueList to a strongly typed List
List<Event> orderedEvents = Event.list(result3.getEntityList());
}
// Prepare queries
val query1 = DataQuery().from(sessionSet)
val query2 = DataQuery().from(eventSet).withKey(Event.key(999L))
val query3 = DataQuery().from(eventSet).orderBy(Event.name)
// Create a new batch request
val batch = RequestBatch()
// Add the data queries to the batch
batch.addQuery(query1)
batch.addQuery(query2)
batch.addQuery(query3)
// Execute batch
dataService.processBatch(batch)
// Get results for each data query
val result1 = batch.getQueryResult(query1)
val result2 = batch.getQueryResult(query2)
val result3 = batch.getQueryResult(query3)
if (result1.status == 200) {
// Convert returned EntityValueList to a strongly typed List
val sessions = Session.list(result1.entityList)
}
if (result2.status == 200) {
// Convert returned EntityValueList to a strongly typed List
val events = Event.list(result2.entityList)
}
if (result3.status == 200) {
// Convert returned EntityValueList to a strongly typed List
val orderedEvents = Event.list(result3.entityList)
}
If an error was encountered during execution of the query, use QueryResult
to get more information.
int status = result1.getStatus();
if (status != 200) {
// Get the exception returned by the provider
DataServiceException dsEx = result1.getError();
ErrorResponse response = dsEx.getResponse();
// Service-defined error code, serving as a sub-status for the HTTP error code
// specified in the response
String serverStatusCode = response.getCode();
// Language dependent error message
String serverMessage = response.getMessage();
// Target of the particular error e.g. the name of the property in error
String target = response.getTarget();
// Server-defined and should only be used in development for OData
String innerDetails = response.getInnerDetails();
// OData allows for server to return a list of error responses
// This is totally server dependent
ErrorResponseList erl = response.getDetails();
...
}
val status = result1.status
if (status != 200) {
// Get the exception returned by the provider
val dsEx = result1.error
val response = dsEx.response
// Service-defined error code, serving as a sub-status for the HTTP error code
// specified in the response
val serverStatusCode = response.code
// Language dependent error message
val serverMessage = response.message
// Target of the particular error e.g. the name of the property in error
val target = response.target
// Server-defined and should only be used in development for OData
val innerDetails = response.innerDetails
// OData allows for server to return a list of error responses
// This is totally server dependent
val erl = response.details
...
}
ChangeSet
¶
A change set is an atomic unit of work that is made up of an unordered group of one or more CUD operations. Note that change sets cannot contain retrieve requests and cannot be nested. The status of a change set is only relevant when a request has failed, in which case the status of the failed request is returned.
The following example shows a change set and a query enclosed in a batch request.
RequestBatch batch = new RequestBatch();
ChangeSet updateChangeSet = new ChangeSet();
// Update 2 events in change set
event1.setLocation("The Hague");
updateChangeSet.updateEntity(event1);
event2.setBanner("http://abc.com/banner");
updateChangeSet.updateEntity(event2);
// Add change set to batch
batch.addChanges(updateChangeSet);
// Add query to batch
DataQuery query = new DataQuery().from(eventSet);
batch.addQuery(query);
// Process batch
eventService.processBatch(batch);
// Retrieve result from query
QueryResult result = batch.getQueryResult(query);
int queryStatus = result.getStatus();
List<Event> events = Event.list(result.getEntityList());
// Check response status for change set
int changeSetStatus = updateChangeSet.getStatus();
// Note: batch.getChangeSet(0) is updateChangeSet
// In a batch, everything is executed in order and
// we can retrieve change sets in the order they are added to the batch
if (changeSetStatus >= 400) {
// Get the exception returned by the provider
DataServiceException dsEx = updateChangeSet.getError();
// Get error response
ErrorResponse response = dsEx.getResponse();
...
}
val batch = RequestBatch()
val updateChangeSet = ChangeSet()
// Update 2 events in change set
event1.location = "The Hague"
updateChangeSet.updateEntity(event1)
event2.banner = "http://abc.com/banner"
updateChangeSet.updateEntity(event2)
// Add change set to batch
batch.addChanges(updateChangeSet)
// Add query to batch
val query = DataQuery().from(eventSet)
batch.addQuery(query)
// Process batch
eventService.processBatch(batch)
// Retrieve result from query
val result = batch.getQueryResult(query)
val queryStatus = result.status
val events = Event.list(result.entityList)
// Check response status for change set
val changeSetStatus = updateChangeSet.status
// Note: batch.getChangeSet(0) is updateChangeSet
// In a batch, everything is executed in order and
// we can retrieve change sets in the order they are added to the batch
if (changeSetStatus >= 400) {
// Get the exception returned by the provider
val dsEx = updateChangeSet.error
// Get error response
val response = dsEx.response
...
}
Using Batch to Create Entities and Associations¶
This is an alternative to performing a deep insert, as described earlier, and is done via content-ID referencing. While OData stipulates that requests within a change set are unordered, using a content-ID reference within the change set should allow the server to execute requests in the proper order.
// Proxy Class
// Create a new event and set its properties
Event event = ...;
// Create 2 new sessions belonging to the new event
Session session1 = ...;
Session session2 = ...;
ChangeSet changeSet = new ChangeSet();
changeSet.createEntity(event);
changeSet.createRelatedEntity(session1, event, Event.sessions);
changeSet.createRelatedEntity(session2, event, Event.sessions);
// The method applyChanges will create a new batch to contain the change set
eventService.applyChanges(changeSet);
// Dynamic API
// Create a new event and set its properties
EntityValue event = EntityValue.ofType(eventType);
...
// Create 2 new sessions belonging to the new event
EntityValue session1 = EntityValue.ofType(sessionType);
...
EntityValue session2 = EntityValue.ofType(sessionType);
...
ChangeSet changeSet = new ChangeSet();
changeSet.createEntity(event);
changeSet.createRelatedEntity(session1, event, eventSessionsProp);
changeSet.createRelatedEntity(session2, event, eventSessionsProp);
// The method applyChanges will create a new batch to contain the change set
dataService.applyChanges(changeSet);
// Proxy Class
// Create a new event and set its properties
val event = ...
// Create 2 new sessions belonging to the new event
val session1 = ...
val session2 = ...
val changeSet = ChangeSet()
changeSet.createEntity(event)
changeSet.createRelatedEntity(session1, event, Event.sessions)
changeSet.createRelatedEntity(session2, event, Event.sessions)
// The method applyChanges will create a new batch to contain the change set
eventService.applyChanges(changeSet)
// Dynamic API
// Create a new event and set its properties
val event = EntityValue.ofType(eventType)
...
// Create 2 new sessions belonging to the new event
val session1 = EntityValue.ofType(sessionType)
...
val session2 = EntityValue.ofType(sessionType)
...
val changeSet = ChangeSet()
changeSet.createEntity(event)
changeSet.createRelatedEntity(session1, event, eventSessionsProp)
changeSet.createRelatedEntity(session2, event, eventSessionsProp)
// The method applyChanges will create a new batch to contain the change set
dataService.applyChanges(changeSet)
Serialization¶
During the Android application development process, developers often have to send Java class objects from one activity to another activity or save some as state information. Use JSON serialization or Android Parcelable
to accomplish this.
OData API supports serializing Java objects as JSON string or Parcelable
.
JSON Serialization¶
// Proxy Class
// Serialization to JSON String
List<Event> events = ...;
String jsonString = ToJSON.entityList(events);
bundle.putString(BUNDLE_KEY, jsonString);
// Deserialization from JSON String
// Note that FromJSON.entityList returns a DataQuery wrapping the JSON string as the result
// When getEvents is invoked, there will not be any network I/O
List<Event> restoredEvents =
eventService.getEvents(FromJSON.entityList(bundle.getString(BUNDLE_KEY)));
// Dynamic API
// Serialization to JSON String
EntityValueList events = ...;
String jsonString = ToJSON.entityList(events);
bundle.putString(BUNDLE_KEY, jsonString);
// Deserialization from JSON String
// Since the DataQuery returned from FromJSON.entityList does not contain from method
// which the query is against, the from method is required in order for
// deserialization to know to what entity type it is deserializing to
EntityValueList restoredEvents =
dataService.executeQuery(
FromJSON.entityList(bundle.getString(BUNDLE_KEY)).from(eventSet)
).getEntityList();
// Proxy Class
// Serialization to JSON String
val events = ...
val jsonString = ToJSON.entityList(events)
bundle.putString(BUNDLE_KEY, jsonString)
// Deserialization from JSON String
// Note that FromJSON.entityList returns a DataQuery wrapping the JSON string as the result
// When getEvents is invoked, there will not be any network I/O
val restoredEvents = eventService.getEvents(
FromJSON.entityList(bundle.getString(BUNDLE_KEY)))
// Dynamic API
// Serialization to JSON String
val events = ...
val jsonString = ToJSON.entityList(events)
bundle.putString(BUNDLE_KEY, jsonString)
// Deserialization from JSON String
// Since the DataQuery returned from FromJSON.entityList does not contain from method
// which the query is against, the from method is required in order for
// deserialization to know to what entity type it is deserializing to
val restoredEvents =
dataService.executeQuery(
FromJSON.entityList(bundle.getString(BUNDLE_KEY)).from(eventSet)
).entityList
Android Parcelable
¶
Generated proxy classes have built-in support for Android Parcelable
.
// Using Parcelable
// Proxy Class
List<Event> events = ...;
bundle.putParcelableArrayList(BUNDLE_KEY,
(ArrayList<? extends Parcelable>)events);
// Convert value from bundle back to proxy class
List<Event> restoredEvents = bundle.getParcelableArrayList(BUNDLE_KEY);
// Using Parcelable
// Proxy Class
val events = ...
bundle.putParcelableArrayList(BUNDLE_KEY,
(ArrayList<? extends Parcelable>)events)
// Convert value from bundle back to proxy class
val restoredEvents = bundle.getParcelableArrayList(BUNDLE_KEY);
Exception Handling¶
Because exceptions may occur for queries or CUD operations, you should put them inside a try/catch statement. If the OData provider returns a response with an HTTP error status code, a DataServiceException
is thrown. You can then examine the server response to determine how the error should be handled.
// Query for a non-existent event
DataQuery query = new DataQuery().withKey(Event.key(99999L));
try {
List<Event> events = eventService.getEvents(query);
} catch (DataServiceException ex) {
// HTTP status code
int status = ex.getStatus();
// Examine the message sent back by server
ErrorResponse response = ex.getResponse();
String serverErrorMessage = response.getMessage();
} catch (Exception unex) {
// unexpected exception handling
}
// Query for a non-existent event
val query = DataQuery().withKey(Event.key(99999L))
try {
val events = eventService.getEvents(query)
} catch (ex: DataServiceException) {
// HTTP status code
val status = ex.status
// Examine the message sent back by server
val response = ex.response
val serverErrorMessage = response.message
} catch (unex: Exception) {
// unexpected exception handling
}
The OData specification defines the error response from the server. This response consists of a code that serves as a substatus to the HTTP status code and a message. The OData component parses the response and makes it available to the application through the getMessage
and getCode
methods of the ErrorResponse
class. See the preceding example for more details.
For exceptions occurring within a batch request, the HTTP status code can be retrieved from the QueryResult
or ChangeSet
. If there is an error, use the getError
method from either class to retrieve the corresponding DataServiceException
. With the DataServiceException
, you can get the ErrorResponse
and use it to get the code and the message from the server. See the earlier section on batch processing for additional code samples.
It is important to understand that in the offline case, the request or batch is executed against the local store. Execution status only indicates success or failure from the point of view of the local store. When the pending requests are uploaded to synchronize the local store with the OData service, they may fail. Resolution of such errors can be complex. Please refer to offline error handling topic for more details.
// For query within a batch
QueryResult result = batch.getQueryResult(query);
int httpStatusCode = result.getStatus();
DataServiceException ex = result.getError();
ErrorResponse response = ex.getResponse();
String serverCode = response.getCode();
String serverMessage = response.getMessage();
// For ChangeSet within a batch
int httpStatusCode = updateChangeSet.getStatus();
DataServiceException ex = updateChangeSet.getError();
ErrorResponse response = ex.getResponse();
String serverCode = response.getCode();
String serverMessage = response.getMessage();
// For query within a batch
val result = batch.getQueryResult(query);
var httpStatusCode = result.status
var ex = result.error
var response = ex.response
var serverCode = response.code
var serverMessage = response.message
// For ChangeSet within a batch
httpStatusCode = updateChangeSet.status
ex = updateChangeSet.error
response = ex.response
serverCode = response.code
serverMessage = response.message