Understanding Keys in Offline OData with SAP Cloud Platform Mobile Services
I work with a tremendous team of developers, and the team of developers to build and support our offline features are no exception. Though the questions about offline and how its used typically don’t come to me, offline is in so many of the conversations I have when it comes to mobile application. There is already some published information around our offline support, and I’ll provide links to those at the bottom of this post, but I wanted to share some further information that our team has put together for those that are developing applications with our offline support.
In this post our team has created some information as it pertains to Keys in Offline OData. As most know OData is the recommended way of getting to SAP data and often non-SAP data, so let’s take a look to see if we can give a better understanding of this.
In OData there are really two identifiers for an entity:
- An entity is uniquely identified within an entity set by the values of its key properties
- An entity is uniquely identified within an OData service by a globally unique entity ID
- By convention, the globally unique entity ID is the entity set plus key values that identify the entity within the entity set (for example, ~/Customers(101)). But this does not have to be the case.
- OData V2 does not do a good job of distinguishing between these two concepts but OData V4 does. The design and implementation in Offline OData was based on the V4 standard in this case.
- In Offline OData, the real primary key in the underlying Ultralite (UL) database is stored in a system column, lodata_sys_entity_id, in each entity type table and is a 20 byte hash of the entity ID. Since currently we only support OData V2 for full offline support, we construct the entity ID by convention in OData during the refresh and then hash it using SHA-1 to convert it to 20 bytes.
- All relationships in the databases, are stored based on these 20 byte hashes rather than key properties. This allows us to store relationships in exactly the same way for all entity types; if we used key values, it would be different for each entity type.
- Using this approach also leads to a natural solution of how to handle key properties for entities created while offline that do not have a real key. Rather than generate non-null key values for locally created entities, we generate an entity ID instead; a unique 20 byte hash. When we get the real entity from the backend we essentially replace the 20 byte local hash with the real one.
- Note that even when key properties are [partially] specified, we still generate our own local hash. The main reason for this is that in some cases OData servers can ignore key values specified by the client.
- The reason leaving keys null and generating entity IDs works seamlessly (or should work seamlessly) with OData is because OData is hypermedia driven. What that means is that the OData protocol returns the links the client needs to use to perform subsequent operations. Most notably in this case is the server returns edit/read links of individual entities. The OData convention is that if the server (an Offline OData app, the OfflineODataProvider/Offline store acts as thh OData server) returns URLs in the payload, then it is those URLs that should be used to perform subsequent operations. So in the Offline OData case, for entities that have been downloaded from the backend, we return edit/read links that follow the convention mentioned above (~/Customers(101)). But for locally created entities, we return an edit/read link that contains our generated entity ID hash (~/Customers(lodata_sys_edt=X’102C0D940871440592E30C31A209F2CE00000000′)). As long as the application uses the URLs returned, it does not need to handle server or local entities any differently; it should be completely opaque.
- It is possible that a not yet updated local entity’s key can clash with one that is downloaded from the backend after the local entity was created. LOData identifies duplicate keys in the proper cases and prevents further operations until the issue is corrected.
- Key properties cannot be modified in OData. Generally speaking, Offline OData follows the same rule (except for one special case as described below). That is, regardless of whether the entity is created locally or downloaded from backend, the entity’s key properties cannot be modified by update requests on a client.For a locally created entity, even if some key properties are null (i.e. the user didn’t provide values for those properties), those key properties cannot be modified an update request.
It is possible that the backend may ignore some key property values or generate new key property values for a new entity uploaded/flushed from client, therefore we may see key property values of a new entity created locally being changed after download/refresh, but that is not treated as being modified by update requests.
Offline OData allows modifying key properties in one exceptional case:
- Offline OData allows modifying key properties of local entities in the error state with update requests. The error state must be a result of a POST failure, meaning that the entities are created locally but encountered a server error when being uploaded/flushed. If an entity was originally downloaded from server, then its key properties can never be modified even it goes into the error state after some updates and upload/flush.Attempting to modify key properties of a local entity not in the error state is not allowed.
Additional Offline oData Topics
- Offline OData Delta Support
- Offline OData Error Handling
- Offline OData Conflict Detection
- Initial Download Sequence Diagram
- Refresh Sequence Diagram