In this blog, I will be talking about offline support for Windows. If you have been following my other blogs (or corresponding blogs for Android and iOS), you are already aware of the concept of a Store. You are probably aware that there is an Online Store and by extension, an Offline Store. Although I haven’t talked about an Offline Store explicitly, I have made a few subtle references to its existence in my previous blogs. An astute reader might have already guessed that an Online Store can be used when the device has network connection. So what about the Offline Store? A common mistake most people make, is that they only associate Offline Store with no network connection. While this is true (Offline Store is used with no network connection), an Offline Store can also be used when there is network connection. In fact, I would recommend using the Offline Store in all cases (if you want offline support) – and not use the Online Store at all. Of course, this would also depend on the business needs. But using the Offline Store regardless of network connectivity reduces the complexity of switching back and forth between Online and Offline Stores. The caveat is that the user might be working with stale data.

Comparing Online and Offline Stores

In the table below, I roughly compare the features of an Online Store and an Offline Store.

Refresh method pushes changes made on the backend to the local ultralite database

Data conflicts – very minimal

Data conflicts – a real possibility

HTTP(S) protocol to transfer data between SMP Server and device

Mobilink protocol to transfer data between SMP Server and device

What happens when I open an Offline Store?

The first time an Offline Store is opened, a lot of things happen. The Offline Store connects to the SMP Server and sends it the defining request. It is very critical to understand what constitutes a defining request. A defining request is an URL representing data fetched by an HTTP GET method that needs to be persisted. An application can have multiple defining requests.

Examples of defining requests… Note that each defining request corresponds to a single GET request.

Request 1 = BusinessPartners?$expand=SalesOrders/Items

Request 2 = SalesOrders(‘0500000002’)?expand=Items

Request 3 = Products?$expand=ProductDetails

Request 1

BusinessPartners

Rows 1, 2, 3

SalesOrders

Rows 1, 2, 3, 4, 5, 6, 7

Items

Rows 1, 2, 3, 4, 5, 6, 7, 8, 9

Data persisted on device

BusinessPartners

Rows 1, 2, 3

SalesOrders

Rows 1, 2, 3, 4, 5, 6, 7

Items

Rows 1, 2, 3, 4, 5, 6, 7, 8, 9

Products

Rows 1, 2, 3, 4

ProductDetails

Rows 1, 2, 3, 4

Request 2

SalesOrders

Rows 3

Items

Rows 4, 5

Request 3

Products

Rows 1, 2, 3, 4

ProductDetails

Rows 1, 2, 3, 4

SMP Server queries the backend using the defining requests. The union of the data retrieved by all the defining requests is used to populate the database on the SMP Server. This prepopulated database is then sent to the device. Note that data is not duplicated on the device – just the union of the data retrieved by all the defining requests is persisted. For efficiency reasons, it is recommended not to have overlapping defining requests. However, even if you have overlapping defining requests, data will not be duplicated on the device.

Lot less happens during subsequent openings of the Offline Store. The application does not connect to SMP Server. Therefore application does not require network connection. Application simply opens the existing database on the device.

Store

Action

Offline Store (first time opening)

Needs network connection

Defining request sent to SMP Server

SMP Server queries backend using defining requests

Creates a new database with backend response

Sends newly created database to device

Offline Store (Subsequent openings)

Does not need network connection

Simply opens existing database on device

Does not connect to SMP Server

How do I create and open an Offline Store?

Creating an Offline Store is straightforward. The constructor does not take any parameters.

this.Store = new SAP.Data.OData.Offline.Store.ODataOfflineStore();

Opening the Offline Store takes ODataOfflineStoreOptions as a parameter

await this.Store.OpenAsync(options);

So, how do I create this “options” parameter? For this, you need to create the ODataOfflineStoreOptions object with the proper values. Thankfully, most values are intuitive. The following code snippet creates the ODataOfflineStoreOptions and populates it with the proper values.

Note: For operations that change state (for example, inserting a new resource) re-issuing the request may result in an undesired state (for example, two orders placed). Set EnableRepeatableRequests property to true to avoid such situations. Note that the backend OData Service must support this feature. SAP Gateway supports this feature.

CRUD operations on Offline Store after opening

CRUD operations can be performed on the Offline Store after successfully opening the Offline Store. The syntax for CRUD operations on an Offline Store is identical to the syntax for an Online Store. The only difference is that the data is retrieved from the locally persisted ultralite database instead of from the backend.

CREATE

var execution = store.ScheduleCreateEntity(entity, collectionName);

var response = await execution.Response;

READ

var execution = store.ScheduleReadEntitySet(collectionName);

var response = await execution.Response;

UPDATE

var execution = store.ScheduleUpdateEntity(copiedEntity);

var response = await execution.Response;

DELETE

var execution = store.ScheduleDeleteEntity(entity);

var response = await execution.Response;

Understanding Flush and Refresh

Flush and refresh allows the locally persisted data to be synchronized with the backend data. Both Flush and Refresh methods require network connection. Flush allows changes made locally on the device to be applied on the backend in an asynchronous fashion. Refresh on the other hand, allows changes made on the backend to be downloaded to the device. An important thing to note when calling the Flush method is that all the changes made locally on the device are submitted. The SDK currently does not support sending part of the changes. However, Refresh method has the option of downloading part of the backend changes based on defining request.

Flush method

Refresh method

Requires network connection

Requires network connection

Changes made locally submitted to backend

Changes made on backend downloaded to device

This call is asynchronous

This call is asynchronous

All changes are submitted – Cannot submit part of the changes

Developer has the option of downloading part of backend changes based on defining request

Call Flush before calling Refresh

Call Flush before calling Refresh

FLUSH

await this.Store.ScheduleFlushQueuedRequestsAsync();

REFRESH

await this.Store.ScheduleRefreshAsync(); OR

await this.Store.ScheduleRefreshAsync(definingrequest);

Some important considerations

Offline support for Windows is only available for Windows Store applications and Windows Phone Store applications. Offline support is not available for Windows desktop .NET applications.

The local database is created in the location given by: Windows.Storage.ApplicationData.Current.LocalFolder. It is essentially the application data directory. iLoData.exe is an utility that is shipped with SMP SDK SP11 onwards. This command line utility can be used to open the local ultralite database and view the contents. This is a great tool for troubleshooting purposes.

The Offline Store takes care of getting and setting the XCSRF token in the MobiLink server component. Nothing additional needs to be done on the client application.

Batch processing is supported in the Offline Store as well.

In the event, the SMP Server is configured with multiple endpoints, then your application can have multiple Offline Stores (one for each endpoint – make sure you supply a unique store name for each Offline Store). You can also have multiple Offline Stores open at the same time.

You can also have Online Store and Offline Store (for the same endpoint) in the same application. Both these stores can be open at the same time. Depending on network availability, the developer can choose which store to use. A lot of times it is easier to only have the Offline Store and use it regardless of network connectivity. Periodically call flush and refresh to sync the data with the backend.

Please feel free to post any comments or questions that you might have. I will try to answer them as soon as I can. Hopefully, these blogs have given you enough insights into the Windows SDK to start building new mobile applications. Good luck !

32 Comments

I am developing Native Android App, consuming OData Services. I want to use offline store options, because most of the time my app will be in offline. But when i try to open offline store my app crashes “Null pointer Exception in gCtx = lgCore.getLogonContext();“.

ODataOfflineStore.globalInit();

lgCtx = lgCore.getLogonContext();

String endPointURL = lgCtx.getAppEndPointUrl();

URL url = new URL(HOST_NAME);

// Define the offline store options.

// Connection parameter and credentials and

// the application connection id we got at the registration

ODataOfflineStoreOptions options = new ODataOfflineStoreOptions();

options.host = “hosturl”;

options.port = “8080”;

options.enableHTTPS = true;

options.serviceRoot= HOST_NAME;

//The logonconfigurator uses the information obtained in the registration

Looks like your option.serviceRoot value is incorrect. The serviceRoot property should not be set as the HOST_NAME. In the Windows example, I have set serviceRoot to be the name of application (com.sap.flight)

options.ServiceRoot = “com.sap.flight”;

If you have additional endpoints, then you will set the serviceRoot to the name of the endpoint.

I had not included a couple of mandatory calls that need to be made when the application starts and application is closed – for offline support. I was going to include them in the sample application and ‘How To… Guide’. Perhaps that is causing the issue.

// This needs to be called when the application starts.

// I would add this line in app.xaml.cs in the OnLaunched event… (and Resuming event)

SAP.Data.OData.Offline.Store.ODataOfflineStore.GlobalInit();

// This needs to be called when the application is closed

// I would add this line in app.xaml.cs in the OnSuspending event…

SAP.Data.OData.Offline.Store.ODataOfflineStore.GlobalFini();

I believe that is causing this issue. Let me know if that helps. I will update the blog soon with these mandatory calls.

I have fixed that issue by referring Visual C++ 2013 Redistribution extension that resolved missing DLL dependencies of SAP.Data.OData.Offline.Store.

Still I am facing some issue.

Error:

A first chance exception of type ‘SAP.Data.OData.Store.ODataNetworkException’ occurred in mscorlib.ni.dll

SAP.Data.OData.Store.ODataNetworkException: [-10210] The operation failed due to an error on the server. —> SAP.Data.OData.Offline.Store.ODataOfflineException: [-10210] The operation failed due to an error on the server.

— End of inner exception stack trace —

at SAP.Data.OData.Offline.Store.ODataOfflineStore.<OpenAsync>d__6.MoveNext()

— End of stack trace from previous location where exception was thrown —

at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)

at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

at System.Runtime.CompilerServices.TaskAwaiter.GetResult()

at SAP.Data.OData.Offline.Store.ODataOfflineStore.<OpenAsync>d__3.MoveNext()

— End of stack trace from previous location where exception was thrown —

at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)

at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

Looks like your options.serviceRoot value is incorrect. The serviceRoot property should not be set as the full path. In the Windows example, I have set serviceRoot to be the name of application (com.sap.flight)

options.ServiceRoot = “com.sap.flight”;

If you have additional endpoints, then you will set the serviceRoot to the name of the endpoint.

I ran your project and was able to reproduce the issue. The problem is that you are not calling the method below… This needs to be called when the application starts up. You also need to call the corresponding GlobalFini() method when the application is suspended.

Can you insert the collection of entities sequentially ? Or you could use a batch request to insert all the entities – it still inserts one entity at a time, but the change set can be treated as a transaction…

We are developing Windows 8.1 Store app and we are using offline store feature to download data and use it for offline purpose.

During synchronization of data, we are getting bulk of records (4k) delta coming from backend oData server during Refresh.

We have seen this delta is processed on backend server in around 10 minutes in access logs.

Unfortunately we get <a:DateTime>2016-07-22T11:30:08.0960521Z</a:DateTime><lid>SAP.Data.OData.Offline.Store</lid><msg>[5018] Finished refreshing the store.</msg>back from await this.Store.ScheduleRefreshAsync(); after 4 and half hours.

Unless you are asking for clarification/correction of some part of the Document, please create a new Discussion marked as a Question. The Comments section of a Blog (or Document) is not the right vehicle for asking questions as the results are not easily searchable. Once your issue is solved, a Discussion with the solution (and marked with Correct Answer) makes the results visible to others experiencing a similar problem. If a blog or document is related, put in a link. Read the Getting Started documents (link at the top right) including the Rules of Engagement.

NOTE: Getting the link is easy enough for both the author and Blog. Simply MouseOver the item, Right Click, and select Copy Shortcut. Paste it into your Discussion. You can also click on the url after pasting. Click on the A to expand the options and select T (on the right) to Auto-Title the url.