Skip to content

Overview

Offline OData provides flexible mechanisms to help developers handle failed requests.

When a request fails against the back-end OData service during an upload operation, the request and any relevant details are stored in the ErrorArchive.

Any subsequent requests that are associated with the same entity are not executed and can be found in the ErrorArchive.

It is very difficult to provide a generic error handling algorithm. Instead, offline application developers should consider the following steps:

  1. Determine the set of entity types whose entities will be modified.
  2. Develop appropriate input validation (syntax and business rules) for all involved entity types.
  3. Determine the business logic errors that can occur outside the control of the application but are inherent in the way the application functions. For example, updating a purchase order when changes in the back end preclude any modification.
  4. Work with the OData service team, if possible, to define error codes, messages, and so on, that the application can easily parse and take action on. Even if a developer knows the OData protocol well, it may not be easy to determine what the issue is using only the information in the ErrorArchive. This is especially true for errors that are related to business logic.
  5. If concurrent conflicts can occur with the offline application, determine how to resolve the conflicts by providing appropriate information for the end users to resolve the conflicts.
  6. Test as much as possible, including performing on-device testing.
  7. For unknown or unexpected errors, determine the best way to collect information for analysis through appropriate logging, without including sensitive data.

Merge Algorithm

The heuristic-based merge algorithm used by Offline OData to compute the fixed requests attempts to combine the details of new requests as far forward as possible, to replace any incorrect data in the original requests with correct data.

Common merge scenarios include the following:

Fixing the Properties of a Failed POST Request

A subsequent PATCH request can be made to the entity (that failed to be created in the back end) with the corrected values. The merge heuristic combines the PATCH and POST requests into a single new POST request containing the corrected values.

// An entity results in an error when being created in the back end,
// that is, the POST request to create it failed.
// Load the entry in the error archive
DataQuery errorArchiveQuery = new DataQuery().from(errorArchiveSet);
EntityValueList errors = eventService.executeQuery(errorArchiveQuery).getEntityList();

// Assume the error is the first entry
EntityValue error = errors.get(0);

// Examine the error message from the OData service.
// From error message, determine reason of the failure and
// resolution, for example, needing to remedy a specific property.
// Load navigation property AffectEntity
eventService.loadProperty(affectedEntityProp, error, null);
EntityValue entity = affectedEntityProp.getEntity(error);
Event eventInError = (Event) entity;

// Update property of the event in error
eventInError.setType("PUBLIC_EVENT");
eventService.updateEntity(eventInError);

// Upload to retry. The merge algorithm will combine the POST request
// and the PATCH request to a single POST request
// with a new value for the Type property
...
// An entity results in an error when being created in the back end,
// that is, the POST request to create it failed.
// Load the entry in the error archive
let errorArchiveQuery : DataQuery = DataQuery().from(errorArchiveSet)

let errors : EntityValueList = eventService.executeQuery(errorArchiveQuery).entityList()

// Assume the error is the first entry
let error : EntityValue = errors.first()

// Examine the error message from the OData service.
// From error message, determine reason of the failure and
// resolution, for example, needing to remedy a specific property.
// Load navigation property AffectEntity
eventService.loadProperty(affectedEntityProp, into: entity)

let eventInError = entity

// Update property of the event in error
eventInError.setType("PUBLIC_EVENT")
eventService.updateEntity(eventInError)

// Upload to retry. The merge algorithm will combine the POST request
// and the PATCH request to a single POST request
// with a new value for the Type property
...

Fixing the Properties of a Failed PATCH Request

A subsequent PATCH that includes the correct values can be made to the entity that failed to be updated in the back end. The merge heuristic combines both PATCH requests into a single new PATCH request containing the corrected values.

// Locate the event in the error state as described in the previous example.
Event eventInError = ...;

// In this case, the fix is to provide the event a new type
eventInError.setType("PUBLIC_EVENT");
eventService.updateEntity(eventInError);

// Upload to retry. The merge algorithm will combine the two PATCH requests
// together to a single PATCH request with a new value for the Type property
...
// Locate the event in the error state as described in the previous example.
let eventInError : Event = ...

// In this case, the fix is to provide the event a new type
eventInError.setType("PUBLIC_EVENT")
eventService.updateEntity(eventInError)

// Upload to retry. The merge algorithm will combine the two PATCH requests
// together to a single PATCH request with a new value for the Type property
...

Discarding a Failed POST Request

A subsequent DELETE can be made to the entity. The merge heuristic deletes the original POST request and nothing is sent to the back end.

// Locate the event in error following the example above.
Event eventInError = ...;

// Decided that the entity shouldn't be created
eventService.deletEntity(eventInError);

// Upload to retry. The DELETE request will void the original POST request
...
// Locate the event in error following the example above.
let eventInError : Event = ...

// Decided that the entity shouldn't be created
eventService.deletEntity(eventInError)

// Upload to retry. The DELETE request will void the original POST request
...

Fixing a PATCH or DELETE Request that Failed Due to a Stale ETag

The application can perform a download operation to obtain the latest version of entity data and an ETag. If appropriate, use only a subset of the defining queries that includes the entity set of the entity in error. If the failed request is resubmitted without further changes, the merge heuristic replaces the stale ETag in the failed request with the fresh one.

If application end users must examine latest data from the OData service to understand what has changed and adjust the current request accordingly, the application needs to revert the error state after saving the existing value of the entity. Once reverted, the offline store has the latest data from the OData service.

A complication arises when there are other errors in the ErrorArchive. All errors must be fixed first, as deleting one entry in the ErrorArchive results in all errors being reverted.

Fixing a DELETE Request

When an entity isn't deleted from the back end because of an incorrect current value for one or more of its properties, you can fix this issue by submitting a PATCH request. The merge algorithm resequences the PATCH request so it is executed before the DELETE request.

// Delete an event. This operation succeeds since there is no business logic locally.
eventService.deleteEntity(event);

// Uploading to OData service will result in error
...

// Locate the entity in error state (see code samples above)
Event eventInError = ...;

// Fix the error by changing the event's type
eventInError.setType("PUBLIC_EVENT_CLOSED");
eventService.updateEntity(eventInError);

// Upload to retry. The PATCH request and POST request will be resequenced
...
// Delete an event. This operation succeeds since there is no business logic locally.
eventService.deleteEntity(event)

// Uploading to OData service will result in error
...

// Locate the entity in error state (see code samples above)
let eventInError : Event = ...

// Fix the error by changing the event's type
eventInError.setType("PUBLIC_EVENT_CLOSED")
eventService.updateEntity(eventInError)

// Upload to retry. The PATCH request and POST request will be resequenced
...

If a DELETE request results in an error, the only way to revert the error state is by deleting the entry from the ErrorArchive. This carries the same complication mentioned in the previous section, that is, fixing other errors in the ErrorArchive.

Limitations

There are some limitations in handling failed requests.

  • Request Order and Type The requests computed by the merge algorithm may not match the order or form of the failed requests. As a result, any back-end side effects that rely on a certain type or order of requests may be affected.

    The merge algorithm may alter batch change sets. Requests in one change set may be moved out of a change set or into another change set. If the transactional boundaries are important, then do not attempt to fix requests. Instead, please revert the error state before applying any new requests to the offline store.

  • Batch Request Deleting from the ErrorArchive (to revert the error state) in a batch request is not supported.

    There are a number of reasons for this, but most importantly, an ErrorArchive deletion is not simply deleting a row from a table. It resets all error information in the store, which can have a large effect on the entire offline store. Other operations should not rely on the success of this ErrorArchive operation to be executed correctly, which is another reason not to use a change set.


Last update: June 7, 2022