Skip to content

Handling 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 going through the following steps:

  1. Determine the set of entity types which entities will be modified.
  2. Develop appropriate input validation (syntax and business rule) 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.

Error State

Any entities or relationships in the offline store that are affected by a failed request are moved into an Error State, which indicates that the state of the entities or relationships in question may not be valid and should be fixed before proceeding.

Entities and relationships that are in an error state are distinguished by the presence of the inErrorState annotation. If a failing request deletes an entity or relationship, those deleted items will reappear in the offline store and contain the additional isDeleteError annotation.

Finding Entities in the Error State

There are two ways to locate entities that are in the error state.

Note

You can not currently locate relationships that are in error state.

The first way is through filtering based on the error state setting in the entities.

1
2
3
4
5
6
7
8
// Count the number of events in error state
DataQuery query = new DataQuery().from(EventServiceMetadata.EntitySets.events)
                .filter(OfflineODataQueryFunction.inErrorState()).count();
long errorCount = eventService.executeQuery(query).getCount();

// Get event(s) in error state
query = new DataQuery().filter(OfflineODataQueryFunction.inErrorState());
List<Event> eventsInError = eventService.getEvents(query);

The second way is to locate the entity that is in the error state due to failure of the request from the ErrorArchive.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// The navigation property AffectEntity can be used to locate the entity
// affected by this error. In other words, the entity that the
// the OData request affected. 

// Retrieve an error from the ErrorArchive
EntityValue error = ...;

// Load the navigation property for a particular error from the ErrorArchive
eventService.loadProperty(affectedEntityProp, error, null);

// Get the affected entity
EntityValue entity = affectedEntityProp.getEntity(error);
if (entity instance of Event) {
    // Cast the EntityValue instance to the generated proxy class
    Event eventInError = (Event) entity;

    // Handle the event
    ...
}

See the code sample at the bottom of the topic Handling Errors and Conflicts for more details of retrieving errors from the ErrorArchive.

There is a current limitation that only one entity is referenced by the navigation property, AffectedEntity. In the case of a relationship operation, two entities are affected but only one (from) can be reached by the navigation property. The other instance (to) can be located using one or both of the following steps:

  1. Examine the error message from the OData service in the ErrorArchive entry to determine the type of relationship operation and the entity type of the other end.
  2. Examine the RequestBody property of the ErrorArchive entry to determine the key of the entity of the other end.

Reverting an Error State

If you perform a DELETE operation against any entry in the ErrorArchive using the deleteEntity() method, it reverts all error states and discards all data from the ErrorArchive.

In addition, the requests associated with entries in the ErrorArchive are removed.

After the DELETE operation, all entities or relationships in the error state appear as though the failed requests were never applied and are no longer in the error state. The ErrorArchive entity set is empty.

1
2
3
4
5
6
7
DataQuery errorArchiveQuery = new DataQuery().from(errorArchiveSet);

// Get the list of errors in the ErrorArchive
EntityValueList errors = eventService.executeQuery(errorArchiveQuery).getEntityList();
if (errors.size() > 0) {
    eventService.deleteEntity(errors.get(0));
} 

Note

The ErrorArchive entity set is local; there is no need to upload after invocation of deleteEntity().

Fixing Requests in an Error State

When an entity or relationship is in an error state, the application can make additional modification requests to correct the data and fix the error before retrying upload.

When fixing failed requests, the sequence of requests that put the offline store in the error state does not matter; the application needs only to apply new requests to adjust the data to be corrected in the offline store. When the application performs an upload, the offline store analyzes the state of the entities and relationships in the offline store, and computes the necessary requests to apply to the back end to achieve the correct state.

The offline store computes these requests based on its own heuristics and may not apply any of the original requests. Conceptually, the application is fixing the data and not the failed requests. It is important that you consider this if there are back-end business logic or side effects that rely on specific OData requests being applied.

If the application does nothing to fix the errors, then the next upload resends the requests without making any changes.

Applying the Fixed Requests

Once the application has made the necessary fixes to the failed requests and has triggered a subsequent upload operation, a merge algorithm of Offline OData computes the fixed requests and then sends them, along with any new requests, to the back end.

If the fixed requests succeed during this upload, they are removed from the ErrorArchive and the affected entities and relationships are moved out of the error state.

If a fixed request fails again or if any new requests fail, they are logged in the ErrorArchive, and the affected entities and relationships are put into the error state.

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.

 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
// 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
...

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Locate the event in error state following the example that was just presented
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
...

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.

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

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.

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

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.

  • 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.

  • 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.