Skip to content

Modifying Offline Data

You can perform OData operations (by executing OData requests) to modify data in an offline store, including creating, deleting and modifying entities as well as relationships.

Modifying offline data is similar to the online case. The difference with offline case is that requests are added to a queue and will be sent (when performing an upload) to the back end later. The changes are local without affecting the back end until you perform an upload.

The code samples in this section use this metadata.

Creating Entities

Understanding Keys

Keys or Key Values

In many cases, the OData service generates key values for key properties when a new entity is created (an entity key may consist of multiple properties). This implies that an entity created offline may not have the key values until a data upload and download have been performed. Although key values may be assigned when the entity is created locally, the values may be omitted or overwritten by the back-end OData service. Therefore, key values assigned locally cannot be treated as permanent.

Think of relational databases, which many OData services are built on. Key properties may be implemented as auto-increment columns in tables. If an entity is not assigned a key when being created locally, when the entity is created on the back end, a table will automatically assign a new number if the key property is implemented as an auto-increment integer column. On the other hand, a key assigned locally can be overwritten by an automatically assigned number in the table.

Internally, the offline store uses a separate entity ID as the key for an entity created locally. The entity can be uniquely identified by either the entity ID or its key values. If all key properties of an entity are non-null (all key properties are supplied values), then the entity can be uniquely identified within its entity set using its key values. See below for recommendations regarding use of readLink and editLink constructed with the entity ID.

Uniqueness of Keys

When you create a new entity locally, the key values can be partially or entirely available if the application specifies any of them. The back-end OData service MAY change them even if the application specifies them. An entity that is created locally that has not been uploaded and downloaded can always be identified by its entity ID, which contains a generated value. The offline store enforces uniqueness for keys of new entities only if all the key properties are specified and are all non-null. If even a single key property is null/unspecified, the offline store does not check for uniqueness. As a result, developers have two choices regarding assignment of key properties for a new entity:

  1. (Recommended) Leave all or some of the key properties null and allow the back end to generate the key.
  2. Set the key properties to values that have no chance of conflicting with existing and new entities from the back-end OData service.

Regardless of whether or not the application specifies key properties when creating new entities, it should never attempt to construct a URL using key properties to identify an entity (either to re-read the entity or to make modifications). The application should always rely on the URLs obtained from the readLink and editLink properties of the entity when re-reading or modifying it. The URLs can uniquely identify the entity. Following this approach ensures that an application does not require separate code to handle re-reading or modifying locally created entities and those downloaded from the back-end service.

Create Entity Without Setting Key Property Values

The sample below calls the constructor without setting default property values. That means, the new event does not have an ID when being created locally. You can create as many as you want events locally in the same way without getting the duplicate key exception. The back end will generate IDs for all events.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Declare a new Event entity without setting properties to default values by passing false to the constructor.
// The entity key, eventID, will be unset 
Event event = new Event(false);

// Set property values
event.setCountry("USA");
event.setName("SAP Summit");

// Create a local Event entity
eventService.createEntity(event);

In the sample below, if you call a generated proxy class constructor without any parameters, the event you create will have its properties set to their respective default values. For example, if the key is an integer, the key property will have 0 as its value. Line 9 unsets ID of the event, giving the back end an opportunity to generate the real ID.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Assign default property values at proxy construction
Event event = new Event();

// Set property values
event.setCountry("USA");
event.setName("SAP Summit");

// Unset entity key explicitly
event.unsetDataValue(Event.eventID);

// Create a local Event entity
eventService.createEntity(event);

If line 9 was not present, the new event would have 0 as its ID. Attempting to create a second event constructed in the same way without setting the key property will cause a duplicate key exception as both the two events would have 0 as ID. Two events with the same ID is not allowed.

To access a newly created entity without key values, retrieve the readLink (read URL) from the entity after the createEntity() method has been performed. The readLink construction uses the locally generated entity ID that uniquely identifies the entity.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Declare a new Track entity without setting properties to default values by passing false to the constructor.
// The entity key, TrackID, will be unset.
Track track = new Track(false);

// Set required property values
track.setName("Keynote");

// Create a local Track entity
eventService.createEntity(track);

// readLink is available after createEntity() call
String readLink = track.getReadLink();
...

// Use loadEntity() to read back via readLink later
// It is important to declare a new entity without the entity key being set to default value
Track readBackTrack = new Track(false);
readBackTrack.setReadLink(readLink);
eventService.loadEntity(readBackTrack);

If you need to apply other query options, for example, select on certain properties or expand on others, create a new DataQuery with appropriate characteristics and use as the second parameter of the loadEntity() method.

Alternatively, you can use the withURL() method, but additional query options require you to directly specify the OData request.

1
2
DataQuery query = new DataQuery().withURL(readLink);
Track track = eventService.getTrack(query);

An entity that is downloaded has its readLink constructed using the actual key from the OData service. In addition, the value derived from the actual key replaces the entity ID generated locally. However, the readLink that is retrieved immediately after createEntity(), with an entity ID, a generated key, can still be usable. Maintaining the entity's ID mapping information helps to simply readLink usage and to avoid a dangling reference. The advantage of readLink is its usage regardless whether the entity has been created, uploaded, or downloaded.

Modifying Entities

Modifying entities is done by calling the updateEntity() method. Normally there will be no combining or reordering of the queued requests. Multiple updateEntity() invocation against the same entity will result in a series of OData requests.

The sample below demonstrates usage of updateEntity() for modifying an entity.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Declare a new Track entity
Track track = ...

// Create a local Track entity
eventService.createEntity(track);

// Update property values
track.setName("General");
...

// Update the entity
eventService.updateEntity(track);

Because a download may take place before any upload, an entity modified locally may receive an update from the OData service. When that happens, the offline store uses the latest state from the OData service and reapplies all the outstanding requests from the queue for that same entity.

You can use either readLink or editLink (by calling getEditLink() on an entity) to identify an entity for modification. In most cases, readLink and editLink have the same URL, but it's recommended that you adhere to their intended usage when leveraging them to read, update or delete.

Empty Update

By default empty PATCH requests, that is, requests that do not change anything, are not sent. Usually such requests will be discarded.

Sometimes such requests are expected to remain and should be sent to the back end. For example, an empty PATCH request may indicate that a parent entity needs to be updated when a child is added. Another example is that, a set of request need to be sent for a group of entities at the same time, although some of those entities haven't been changed.

To force an empty PATCH request to be sent, provide a RequestOptions instance with sendEmptyUpdate property set to true when performing the PATCH request.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Locate the entity to send an empty `PATCH`
Track track = ...

// Do other jobs
...

// Set request options
RequestOptions options = new RequestOptions();
options.setSendEmptyUpdate(true);

// Since sending empty update is enabled, the call would send a PATCH request with empty request body
eventService.updateEntity(track, HttpHeaders.empty, options);

Deleting Entities

You can delete an entity, no matter it is downloaded from the OData service or created locally and not uploaded yet.

1
2
3
4
5
6
7
// Get editLink of the entity to delete
String editLink = ...

// Delete the entity using the editLink
Track trackToDelete = new Track(false);
trackToDelete.setEditLink(editLink);
eventService.deleteEntity(trackToDelete);

A download may take place before any upload. An entity deleted locally remains in deleted mode even if the download informs that the entity has been deleted from the OData service. This means a queued delete request will cause a failure when it is uploaded. A second upload and download will resolve this issue.

Managing Relationship Between Entities

There are multiple ways to create relationships based on OData specification. In general, you can do any of the following:

  1. Create relationship between existing entities.

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

  3. Create new entities and establish relationships between them.

Between Existing Entities

Frequently, this approach is used with a many-to-many relationship where entities on both ends of the relationship can exist independently. For example, an event can exist without any features and a feature can exist without any associated event.

To create relationship between existing entities, use the createLink() method from DataService or a subclass. On the other hand, use the deleteLink() method to delete a relationship.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Locate the event to be linked
Event event = ...; 

// Locate the feature to be linked with the event
Feature feature = ...;

// Link them together
eventService.createLink(event, Event.features, feature);

...

// Delete the relationship between the feature and the event
eventService.deleteLink(event, Event.features, feature);

Note

Since the cardinality of an event-feature relationship is many-to-many, relationship between an event and a feature can be created or deleted, but cannot be updated.

You can also use createLink() and deleteLink() to create/delete relationships between entities which relationship cardinality is zero-or-one or many. You can perform updates with either createLink() or updateLink(). Use of updateLink() verifies that the end being updated has a cardinality of zero-or-one.

In this example, there is only one global theme per event:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Locate the global theme for the event
GlobalTheme theme = ...;

// Establish a relationship between the event and the global theme
eventService.createLink(event, Event.theme, theme);

// Use another theme
GlobalTheme anotherTheme = ...;

// Link the event to the other theme
eventService.updateLink(event, Event.theme, anotherTheme);

Create a New Entity and Associate with an Existing Entity

There are two approaches to create a new entity and associate it with an existing entity. Each approach generates a different OData request. Functionally, there is no difference. However, some OData services may only accept one of the two approaches.

Create with Bind

This approach creates the new entity against its own entity set. The entity is bound to an existing entity with which the relationship is to be established.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Declare a new session
Session session = new Session(false);

// Set session properties
...

// Locate the event to associate with
Event event = ...;

// Bind the new session to the existing event
session.bindEntity(event, Session.event);

// Create the session
eventService.createEntity(session);

This approach creates the new entity against the navigation property of an existing entity with which the relationship is to be established.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Declare a new session
Session session = new Session(false);

// Set session properties
...

// Locate the event to associate with
Event event = ...;

// Create new session and associate with the specified event
eventService.createRelatedEntity(session, event, Event.sessions);

Deep Insert

Deep insert is an approach to create an entity with inline definitions of related entities. Support of this varies between OData services. Offline OData also has some limitations with deep insert, see Limitations with Deep Insert.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Declare a  new event 
Event newEvent = new Event(false);

// Set properties for the new event
...

// Declare a session belonging to the event
Session newSession = new Session(false);

// Set properties for the new session
...

// Link event to session
newSession.setEvent(newEvent);

// Create a new session with the event in-line 
eventService.createEntity(newSession);

Note that due to Offline OData component’s limitation, you cannot create a new event with a number of sessions using deep insert.

Batch and Change Set

This approach takes advantage of OData batch and change set. A batch can contain multiple queries and change sets. A change set can contain multiple requests that make changes to data. Creating multiple entities and establishing relationships between them can be done using a feature of OData change set whereby later requests can reference entities created by former requests. As a result, in the same change set, you can create a parent entity first, and then create its child entities by referencing it.

 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
RequestBatch batch = new RequestBatch();
ChangeSet changeSet = new ChangeSet();

// Declare a new event
Event newEvent = new Event(false);

// Set properties for new event
...

// Declare a new session
Session newSession = new Session(false);

// Set properties for new session
...

// Create the new event in change set
changeSet.createEntity(newEvent);

// Create the new session in change set. This indicates that the session is dependent 
// upon the creation of the event.
changeSet.createRelatedEntity(newSession, newEvent, Event.sessions);

// Add change set to batch
batch.addChanges(changeSet);

// Execute the batch request
eventService.processBatch(batch);

Unmodifiable Requests

While Offline OData may alter requests as needed, you can mark some requests as unmodifiable so Offline OData will never alter them.

Queued requests are uploaded and executed in First In, First Out (FIFO) order. There is normally no combining of requests, for example, multiple requests that update the same entity won't be combined. However, during error handling, requests that are in an error state may be combined with new requests to remedy the error. Further explanation of error handling is found in Handling Errors and Conflicts. If specific requests against the OData service should not be combined, developers must indicate this with unmodifiableRequest request option set to true when such requests are executed.

For example, the status of an event must be created in NEW state. If you combine any updates (for example changing the state to IN PROCESS) with the create, the create may fail should the status not being NEW. Therefore, the first create request must be unmodifiable. The following code illustrates how to mark a request as unmodifiable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Declare a new event
Event event = new Event(false);

// Set properties for the event
...

// Make the request for creating the new event unmodifiable
OfflineODataRequestOptions options = new OfflineODataRequestOptions();
options.setUnmodifiableRequest(true);

// Create the new event. This request cannot be combined with other requests 
Service.createEntity(newEvent, HttpHeaders.empty, options);

Local Information About Entities and Relationships

The offline store exposes information about whether or not entities and relationships are local.

Entities

Entities are referred to as local when they are created or modified offline, as the changes exist only locally on the device. An offline store exposes information about whether or not entities are local. The following code fragment demonstrates how to query for all tracks with a filter to retrieve only tracks that are created or updated locally:

1
2
3
4
5
6
7
8
9
DataQuery query = new DataQuery().filter(OfflineODataQueryFunction.isLocal());
List<Track> tracks = eventService.getTracks(query);
Track track = tracks.get(0);

// This shows how to check if an entity is local
if (track.isLocal()) {
    // Process the local track
    ...
}

Relationships

Relationships are referred to as local when they are created or modified offline, as the changes exist only locally on the device.

Note

Currently there is no support for querying whether a relationship is local.