Handling Errors and Conflicts

Offline applications should be designed with ability to handle any errors and conflicts.

Offline OData Errors

Offline OData errors generally fall into the following categories:

  • Network errors Occur when communication with the server is interrupted during an upload or download operation.

    You can often resolve network errors by retrying the operation at a later time. However, if network errors occur due to server-side application configuration, ensure that you have specified the correct settings.

  • Contract violation errors Occur when the application attempts to perform an invalid operation, such as sending a malformed request or attempting to call API methods without first opening the offline store. Such operations are rejected by offline store, and therefore don't impact the store; however, the application should handle the operations in a way that they can be corrected.

  • Business logic failures Occur if business logic at the OData service prevents a change that is allowed at the offline store. The error is recorded in the ErrorArchive, an Offline OData specific entity set containing detailed information about errors. The application can obtain the body of the original request from ErrorArchive.

Offline OData Conflicts

Conflicts are specific types of errors that arise when multiple clients simultaneously modify the same data in the back end. An offline application should include a process that either avoids conflicts, or logs and resolves them when they arise.

OData services use ETags to identify conflicts. An ETag is an identifier that is assigned to a specific version of an entity. When an entity is modified, a new ETag is assigned.

To use ETags to detect conflicts in your application:

  1. The application retrieves an entity, along with its ETag, from the offline store.

  2. When an update request is made, the application sends the request, along with the original ETag, to the offline store.

  3. If the ETag that is provided with the update request matches what is currently in the offline store, the update is performed locally and a new ETag is generated. The request and the ETag are added to the request queue. If the ETag does not match, an error is generated.

  4. When an upload is performed, the request and the ETag are sent to the OData service. If the ETag matches the one in the OData service, the update is performed and the updated entity plus a new ETag are retrieved by the application during the next download. If the ETag does not match, the request will fail, the error is put in the ErrorArchive and the application then determines how to proceed.

Usually when an update results in an ETag conflict, the offline store may need to present the latest state from the back end with the modified state so that the application can decide whether additional changes are necessary. A download, possibly with a limited set of defining queries, might need to be done. When the download completes, the offline store has the latest ETag and data. From the offline store, the application can only see the latest state from the back end with the associated pending requests applied. If you require the latest state from the back end for proper handling of the conflict, you may choose to remove entries in the ErrorArchive. This will cause removal of all requests in error as well and associated entities returning to server state. This will be discussed further in Handling Failed Requests.

Accessing the ErrorArchive

The ErrorArchive logs business logic errors and ETag mismatch errors. An offline application can access the ErrorArchive to allow application users to view, delete, or resolve errors within the ErrorArchive.

The ErrorArchive is exposed to the application as an OData entity set and is accessible through the OData API in the same way that the application accesses any other entity sets from the offline store.

Besides the dynamic API, a built-in proxy class named OfflineODataErrorArchiveEntity enables you to easily access the ErrorArchive.

ErrorArchive Entity Properties

Each entity in the ErrorArchive entity set has the following properties:

Name Type Description
RequestID Edm.Int64 An incrementing unique integer assigned to each request.
CustomTag Edm.String The value of the custom tag field of the offending request (if a value for the custom tag field was specified when sending the request).
HTTPStatusCode Edm.Int32 The HTTP status code returned in the response of the OData service.
Code Edm.String The vendor error code returned in the response of the OData service.
Message Edm.String The vendor error message returned in the response of the OData service.
InnerError Edm.String The vendor inner error returned in the response of the OData service.
Domain Edm.String The domain of the error.
RequestMethod Edm.String The HTTP method of the offending request (POST, PUT, PATCH, DELETE, and so on).
RequestURL Edm.String The URL of the offending request.
RequestBody Edm.String The payload of the offending request.
AffectedEntity Navigation Property A navigation property that navigates from an ErrorArchive entity to an entity in the offline store that is affected by the error.

The following code example shows how to access ErrorArchive (if some errors have occurred) using the dynamic API:

// Set up the entity set, entity type and properties for ErrorArchive
EntitySet errorArchiveSet = eventService.getEntitySet("ErrorArchive");
EntityType errorArchiveType = errorArchiveSet.getEntityType();

// Navigation property
Property affectedEntityProp = errorArchiveType.getProperty("AffectedEntity");

// Structural properties
Property requestIDProp = errorArchiveType.getProperty("RequestID");
Property requestBodyProp = errorArchiveType.getProperty("RequestBody");
Property httpStatusCodeProp = errorArchiveType.getProperty("HTTPStatusCode");
Property codeProp = errorArchiveType.getProperty("Code");
Property messageProp = errorArchiveType.getProperty("Message");
Property requestMethodProp = errorArchiveType.getProperty("RequestMethod");
Property requestURLProp = errorArchiveType.getProperty("RequestURL"); 

DataQuery errorArchiveQuery = new DataQuery().from(errorArchiveSet);

// See if there are any errors in the ErrorArchive
long nErrors = eventService.executeQuery(errorArchiveQuery).getCount();

// Get the list of errors in the ErrorArchive
EntityValueList errors = eventService.executeQuery(errorArchiveQuery).getEntityList(); 

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

// Check for Http Status Code returned
int httpStatusCoce = httpStatusCodeProp.getInt(error));

// Error message
String message = messageProp.getString(error);

// Request method: POST, PUT, DELETE, et cetera
String requestMethod = requestMethodProp.getString(error);

// Request URL (OData request)
String requestURL = requestURLProp.getString(error);

The sample below shows how to access ErrorArchive using OfflineODataProvider and the built-in proxy class OfflineODataErrorArchiveEntity.

// Get entities from the ErrorArchive entity set
List<OfflineODataErrorArchiveEntity> errorArchiveEntities = provider.getErrorArchive();

// Access each error
for(OfflineODataErrorArchiveEntity entity : errorArchiveEntities) {
    long id = entity.getRequestID();
    String requestBody = entity.getRequestBody();

    // Use the values