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)
    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 an entityset, 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 each FILE_DOWNLOAD inside FILE_DOWNLOADSET, Offline will follow its media_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 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:

    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 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 -> {
            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 to BitmapDrawable 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, 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:

    EntityValue.getEntityType().getStreamProperties().length() > 0
    
    EntityValue.entityType.streamProperties.length() > 0
    

    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();