Skip to content

Using the OData API

This section covers how to use the OData API to perform common queries, CUD operations, and service operations. Each section includes code for using generated proxy classes with Dynamic API followed by another code snippet for the same 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 sub class 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 executQuery in their implementation. It is, therefore, important to have a good understanding of the DataQuery class to query an OData service provider.

Note 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: \Type e.g. eventType
  • Entity Set: \Set e.g. eventSet
  • Property: \Prop e.g. eventEventIDProp

  • Query

    Collection/Entity Set

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // OData Query: /Events
    
    // Proxy Class
    // Use generated service class getter methods 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));
        ...
    }
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // 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
    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

    Client determines the size of a page and use the skip and top query options to request for a specific page. This is known as client driven paging.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // 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();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // 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 just the nature of paging against a changing collection.

    InlineCount

    A system query option to indicate 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 user to retrieve anyone of the page or the last page.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 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 also be returned
    // Cast them to Event, the generated strongly typed proxy class for event
    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 also be returned
    EntityValueList events = result.getEntityList();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // OData: /Events?$inlinecount=allpages&$top=50&$skip=0
    
    // Proxy Class
    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 also be returned
    val events = result.entityList
    

    Serer 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, client can only read the next page via nextLink.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // Proxy Class
    DataQuery pagedQuery = new DataQuery()
        .from(EntitySets.events)
        .page(10);
    QueryResult result = eventService.executeQuery(pagedQuery);
    List<Event> events = Event.list(result.getEntityList());
    // Get a 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 a 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
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // Proxy Class
    var pagedQuery = DataQuery()
        .from(EntitySets.events)
        .page(10)
    val result = eventService.executeQuery(pagedQuery)
    val events = Event.list(result.entityList;
    // Get the a 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 a 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.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // 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();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // 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.

    1
    EntityValue event = dataService.executeQuery(query).getOptionalEntity();
    
    1
    val event = dataService.executeQuery(query).optionalEntity
    

    Filter

    A filter can be created to retrieve the subset of a collection that satisfies its criteria. An empty collection will be returned should no entity within the collection satisfies the criteria. The withKey method of DataQuery essentially generates a filter using the entity key.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    // 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();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // 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.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // 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();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // 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 satisifies a specific query. Note that the list of qualifying events is not returned, just the count.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 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();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 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.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // 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();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // 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 to invoke expand multiple times. The following code fragments demonstrate how to do this using proxy classes.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // 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);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // 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.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 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);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 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

    System query option orderBy can be specified to sort the returned set of entities.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // 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();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // 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.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 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();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 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

    The system query option select specifies a subset of the structural properties to be returned with the query.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // 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();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // 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 none is set, an exception will be thrown.

    Using Entity Key

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 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);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Proxy Class
    Event event = new Event();
    event.setReadLink("...");
    eventService.loadEntity(event);
    
    // Dynamic API
    EntityValue event = EntityValue.ofType(eventType);
    event.setReadLink("...");
    dataService.loadEntity(event);
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 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. Hence, the following code will likely result in an entity not found exception due to the EventID being set to its default - zero.

    1
    2
    3
    4
    5
    6
    7
    8
    Event event = new Event();
    // Likely result in an entity not found exception as EventID is 0L
    eventService.loadEntity(event);
    
    // Create a new event without defaults for any property
    event = new Event(false);
    // An exception will be thrown as no identification is provided
    eventService.loadEntity(event);
    
    1
    2
    3
    4
    5
    6
    7
    8
    var event = Event()
    // Likely result in an entity not found exception as EventID is 0L
    eventService.loadEntity(event)
    
    // Create a new event without defaults for any property
    event = Event(false)
    // An exception will be thrown as no identification is provided
    eventService.loadEntity(event)
    

    LoadEntity can be used to lazy load properties, especially navigation properties for an entity that has already been retrieved. For example, the list of features of an event is to be retrieved when the details of the event is to be shown avoiding the use of potentially expensive expand query option in the initial query.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    // Proxy Class
    // An event is retrieved via a query, 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
    EntityValue event = ...;
    DataQuery expandQuery = new DataQuery()
        .expand(eventFeaturesProp, eventThemeProp);
    dataService.loadEntity(event, expandQuery);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // Proxy Class
    // An event is retrieved via a query, 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
    val event = ...
    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 done via loadProperty. In the example below, we are loading top 50 sessions belonging to the event sorted by the name of the track associated with each sessions.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // Proxy Class
    // An event is retrieved via a query
    Event event = ...;
    // Create a path to represent the name of 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
    EntityValue event = ...;
    DataPath path = sessionTrackProp.path(trackNameProp);
    DataQuery sortQuery = new DataQuery().expand(sessionTrackProp).top(50).orderBy(path);
    dataService.loadProperty(eventSessionsProp, event, sortQuery);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // Proxy Class
    // An event is retrieved via a query
    val event = ...
    // Create a path to represent the name of 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
    val event = ...
    val path = sessionTrackProp.path(trackNameProp)
    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.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // 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();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // 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
    

    Custom Query

    Developers who are familiar with OData protocol can specify the OData requests directly. However, the correctness of the requests and using the QueryResult appropriately are the responsibility of the developers as well. In addition, the query options described previously e.g. filter, expand, etc., will be ignored for a custom query.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // 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
    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();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // 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
    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 done on an entity retrieved from the provider. However, all three operations can use a new entity as well.

    CreateEntity

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 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...");
    eventStartDateTimeProp.setValue(event, LocalDateTime.of(2018, 7, 4, 8, 15, 0));
    ...
    dataService.createEntity(event);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 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...")
    eventStartDateTimeProp.setValue(event, LocalDateTime.of(2018, 7, 4, 8, 15, 0))
    ...
    dataService.createEntity(event)
    

    UpdateEntity

    The typical flow for update is: 1. Retrieve the entity 2. Examine/Display properties to be changed 3. Set properties to thier new values 4. Invoke updateEnitty method passing in the entity

    The library by default uses merge and send only changed properties to the OData service.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // 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);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // 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, properties can be changed by: 1. Construct a new entity 2. Set either prinmary key or editLink 3. Invoke rememberOld method which save the current value of all properties 4. Set properties to their new values 5. Invoke UpdateEntity method passing in the new entity

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // 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);
    eventEventIDProp.setLong(event, 1000L);
    event.rememberOld();
    eventCountryProp.setString(event, "Canada");
    dataService.updateEntity(event);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // 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)
    eventEventIDProp.setLong(event, 1000L)
    event.rememberOld()
    eventCountryProp.setString(event, "Canada")
    dataService.updateEntity(event)
    

    If you want to use replace instead of merge semantic for a particular update, pass an RequestOptions as the second parameter. Since the entire entity is replaced, be sure to first read the existing entity before changing properties.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // 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);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // 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, it is best to turn off the merge option for the OData online/offline provider:

    1
    provider.getServiceOptions().setSupportsPatch(false);
    
    1
    provider.getServiceOptions().setSupportsPatch(false)
    

    DeleteEntity

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // 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);
    eventEventIDProp.setLong(entityValue, 1000L);
    dataService.deleteEntity(entityValue);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // 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)
    eventEventID.setLong(entityValue, 1000L)
    dataService.deleteEntity(entityValue)
    

    If you want to delete multiple entities, you can use the deleteByQuery method. Since 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.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // 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);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // 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 based on OData. We can classify them into following:

    1. Establish or maintain relationship between existing entities

    2. Create a new entity and establish relationship with an existing one

    3. Create new entities and establish relationship between them

    Note

    In some data model, the foreign keys are exposed as a property so establishing relations can be done by setting the property to the entity key value of the related entity. However, with many-to-many relations, we rely on the 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, bi-directional many-to-many relations require multiple actions. Please refer to offline documentation for additional information.

    Establish a relation between two entities. The navigation property can have a to-one or to-many cardinality.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    // Proxy Class
    // Load the event that 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 that 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);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 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 that 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 that 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)
    

    Delete a relation between two entities. The navigation property can have to-one or to-many cardinality.

    1
    2
    3
    4
    5
    // Proxy Class
    eventService.deleteLink(event, Event.features, feature);
    
    // Dynamic API
    dataService.deleteLink(event, eventFeaturesProp, feature);
    
    1
    2
    3
    4
    5
    // 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.

    This method can only be used of the navigation property has 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.

    1
    2
    3
    4
    5
    // Proxy Class
    eventService.updateLink(event, Event.Theme, anotherFeature);
    
    // Dynamic API
    dataService.updateLink(event, eventThemeProp, anotherFeature);
    
    1
    2
    3
    4
    5
    // Proxy Class
    eventService.updateLink(event, Event.Theme, anotherFeature)
    
    // Dynamic API
    dataService.updateLink(event, eventThemeProp, anotherFeature)
    

    Create New Entity and Associate with an Existing Entity

    There are two ways to accomplish this based on OData. Functionally, they are the same. However, 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 whom the relationship is to be established.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // OData: POST /Sessions
    // Proxy Class
    // Create a session and associate it with an existing event
    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
    EntityValue session = EntityValue.ofType(sessionType);
    // Set properties...
    // Locate event to be associated with
    EntityValue event = ...;
    session.bindEntity(event, sessionEventProp);
    dataService.createEntity(session);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // OData: POST /Sessions
    // Proxy Class
    // Create a session and associate it with an existing event
    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
    val session = EntityValue.ofType(sessionType)
    // Set properties...
    // Locate event to be associated with
    val event = ...
    session.bindEntity(event, sessionEventProp)
    dataService.createEntity(session)
    

    Creates the new entity against navigation property of the entity with whom the relationship is to be established. The difference between this and bindEntity is that the operation is against the entity set of the entity to be associated with.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // OData: POST /Events(1000L)/Sessions
    
    // Proxy Class
    // Create a session and associate it with an existing event
    Session session = new Session(false);
    // set properties...
    // Locate the event 1000 to associate with
    Event event = ...;
    // Bind the new session to the specified event
    eventService.createRelatedEntity(session, event, Event.sessions);
    
    // Dynamic API
    EntityValue session = EntityValue.ofType(sessionType);
    // Set properties...
    // Locate the event 1000 to associate with
    EntityValue event = ...;
    dataService.createRelatedEntity(session, event, eventSessionsProp);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // OData: POST /Events(1000L)/Sessions
    
    // Proxy Class
    // Create a session and associate it with an existing event
    val session = Session(false)
    // set properties...
    // Locate the event 1000 to associate with
    Event event = ...
    // Bind the new session to the specified event
    eventService.createRelatedEntity(session, event, Event.sessions)
    
    // Dynamic API
    val session = EntityValue.ofType(sessionType)
    // Set properties...
    // Locate the event 1000 to associate with
    val event = ...
    dataService.createRelatedEntity(session, event, eventSessionsProp)
    

    Create New Entities with Associations

    Can be accomplished by a deep insert or the use of batch processing. Batch processing will be discussed in a subsequent section.

    Deep Insert

    Refers to the creation an entity that includes related entities. The operation is atomic and either all are created or none is. It is most often used to create hierarchical data e.g. sales order with its line items but can also be used for non-containment relations.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // 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);
    ...
    EntityValueList sessions = new EntityValueList();
    sessions.add(session1);
    sessions.add(session2);
    eventSessionsProps.setEntityList(event, sessions);
    // Create new event with associated sessions
    dataService.createEntity(event);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // 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)
    ...
    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.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // Proxy Class
    Long count = eventService.getSessionAttendeesCount(session.getSessionID());
    
    // Dynamic API
    // Use DataService executeQuery
    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);
    Long count = LongValue.toNullable(dataService.executeQuery(query).getResult());
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // Proxy Class
    val count = eventService.getSessionAttendeesCount(session.getSessionID())
    
    // Dynamic API
    // Use DataService executeQuery
    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)
    val count = LongValue.toNullable(dataService.executeQuery(query).result)
    

    If the service operation returns a list of entites, you can use getEntityList instead of getResult.

    Alternately, use the convenience method executeMethod to simplify invocation.

    1
    2
    3
    4
    5
    6
    7
    // Use DataService executeMethod
    DataMethod dataMethod = dataService.getDataMethod("getSessionAttendeesCount");
    Long count = LongValue.toNullable(
        dataService.executeMethod(
            dataMethod,
            new ParameterList(1).with("SessionInstanceID", LongValue.of(sessionInstanceID))
        ));
    
    1
    2
    3
    4
    5
    6
    7
    // 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 it is 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 for a batch is always 202. No exception is thrown by processBatch should any of the queries or change sets within it failed. It is the responsibility of the developers to check the status of queries or change sets via getStatus.

    Query

    The data query added to the batch must specify the entity set that it is querying against.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // 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());
    }
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // 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 there is an error encountered during execution of the query, we can get more information from the QueryResult.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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, serves as a sub-status for the HTTP error code
        // specified in the response.
        String serverStatusCode = response.getCode();
        // Languarge 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 response. This is totally
        // server dependent
        ErrorResponseList erl = response.getDetails();
        ...
    }
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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, serves as a sub-status for the HTTP error code
        // specified in the response.
        val serverStatusCode = response.code
        // Languarge 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 response. 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 of the CUD operations. Please note that change sets cannot contain retrieve requests and cannot be nested. The status of a change set is only relevant when a request within has failed, in which case, is the status of the failed request.

    The following example shows a change set and a query enclosed in a batch request.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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 for response status for the ChangeSet
    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 that 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();
        ...
    }
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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 for response status for the ChangeSet
    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 that 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 perform deep insert described earlier and is done via content-id referencing. While OData stipulates that requests within change set are unordered but the use of Content-ID reference within the change set should allow server to execute them in proper order.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 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);
    // Set properites for the new sessions
    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);
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 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)
    // Set properites for the new sessions
    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 saving some as state information. To do that developers can use JSON serialization or Android Parcelable.

    OData API supports serializing Java objects as JSON string or parcelable.

    JSON Serialziation

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 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
    // Since the DataQuery returned by FromJSON.entityList does not contain from which
    // Entity Set 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();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 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
    // Since the DataQuery returned by FromJSON.entityList does not contain from which
    // Entity Set 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.

    1
    2
    3
    4
    5
    6
    7
    8
    // 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);
    
    1
    2
    3
    4
    5
    6
    7
    8
    // 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, it is recommended that you put them within a try/catch. 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 error should be handled.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // 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
    } 
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // 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 sub status 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 an earlier section on batch processing for additional code samples.

    It is important to understand that in 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 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.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // 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();
    
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // 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