Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
miltonc
Product and Topic Expert
Product and Topic Expert
0 Kudos
OverviewBackendApp ConfigurationAndroidiOS
MBO iOS AppOData iOS App

OData iOS Content:

Generated code in the MBO Object API provided means to read from the local ultralite / SQLite database. Predefined queries were available to read from the MBOs.  For example, by default all MBOs are created with a findAll method that allows you to read all the entries of the MBO.  In addition, findBy methods allowed you to read filtered sets of the MBO.

Reading in OData SDK

While both the MBO Object API and the harmonized OData SDK read from a local database, there are some significant differences between the 2 approaches.  The table below lists some of the differences between the 2 approaches.

Reading data using MBO Object API

Reading data using harmonized OData SDK

Predefined queries (eg. findAll, findBy) exists

No predefined queries exists

Reads are synchronous in nature

Reads are asynchronous in nature

SUPObjectList object is returned during a read operation

id<SODataEntitySet> object is returned during a read operation

Custom SQL queries can be executed

Custom OData queries can be executed

The challenge with migration lies in the fact that the read operation returns a completely different object in MBO Object API than the harmonized OData SDK.  In addition, the nature of the call is asynchronous in the harmonized OData SDK.

Approach to migrating read operations

One of the approach to migrating read operations is to create the predefined queries that already exist in the MBO Object API in the harmonized OData SDK.  In essence, for each entityset that is available in the harmonized OData SDK, you must implement a corresponding findAll, findBy etc. method.

businessPartners = (SUPObjectList *)[CentralMBODesignBusinessPartner findAll];

Because the findAll, findBy etc. methods are synchronous in nature, the resultset is immediately assigned to a variable.  However, in the harmonized OData SDK, the read operation is asynchronous in nature. So, there is a little bit of complexity in assigning it to a variable.

In the OData world, operations are performed on the entityset by invoking a combination of HTTP methods, query string parameters and / or an XML body.  For example, the BUSINESSPARTNER entityset can support query, read, create, update and delete operations.  Because the query, read, create, update and delete operations are common for all entitysets, it makes sense to have a base class (EntityCollection) that implements all these methods.  Also, for migration purposes we can create a protocol with optional methods found in the MBO Object API (for example, findAll, findBy etc.). BUSINESSPARTNER and other entitysets can optionally implement these methods.

Optimizing read operations

There are a couple of ways to optimize read operations in the harmonized OData SDK.  When implementing the findAll, findByPrimarykey, findByQuery etc. methods on an entityset, it is best to store the resultset during success callback to a variable.  This in-memory representation of the OData entityset can then be accessed anytime from anywhere in the sample application.  This works best when the resultset is not extremely large.

- (void)findAll

{

   [super queryOperation:@"BusinessPartners" withCompletion:^(id<SODataEntitySet> entities, id<SODataRequestExecution> requestExecution, NSError *error)

   {

if (entities)

      {

        self.CACHE = entities; // for later use from anywhere in application

        [[NSNotificationCenter defaultCenter] postNotificationName:kBusinessPartnerQueryFinished object:nil];

}

   }];

}

Reading Child entitysets

In the MBO Object API, simply calling findAll on the parent MBO is enough to read the corresponding child MBOs as well.  For example, the following findAll method reads data from the parent BusinessPartner MBO.

[self setBusinessPartners:(SUPObjectList *)[CentralMBODesignBusinessPartner findAll]];

BusinessPartner MBO has a property that holds all the child SalesOrder MBOs.

SUPObjectList *_salesOrders;

Since the BusinessPartner MBO is related to the SalesOrder MBO, it is easily retrieved as a property of the BusinessPartner MBO itself.  Note that we are not making a separate call to read the SalesOrder MBO.  In a similar fashion, SalesOrderItems can be retrieved as a property of SalesOrder MBO itself.

The approach to reading child entitysets is different in the harmonized OData SDK.  While you can adopt a similar approach using the $expand clause in harmonized OData SDK, parsing the resultset and even passing the query parameter is a little confusing.  So, it’s easier to simply execute a separate call to read the child entitysets.  The ultralite database which acts as the OData producer on the device allows read operations on all the entitysets – even if it’s actually not possible on the backend OData Service.

Binding UI controls

As mentioned earlier, the read operation in MBO Object API returns a SUPObjectList object.  The item at a given index can then easily be retrieved and cast to the MBO object itself.  The properties of the MBO object can then be bound to UI controls.

CentralMBODesignBusinessPartner *partner = (CentralMBODesignBusinessPartner *)[[self businessPartners] item:indexPath.row];


cell.textLabel.text = partner.COMPANY_NAME;

cell.detailTextLabel.text = partner.WEB_ADDRESS;

However, the read operation in harmonized OData SDK returns an id<SODataEntitySet> object.  The item at a given index can then easily be retrieved.  The properties of the id<SODataEntity> object can then be retrieved by knowing the attribute name and can be bound to UI controls.

id<SODataEntity> entity = [self.sortedEntities objectAtIndex:[indexPath row]];

   

NSString *companyName = (NSString *)[(id<SODataProperty>)entity.properties[@"CompanyName"] value];

NSString *webAddress = (NSString *)[(id<SODataProperty>)entity.properties[@"WebAddress"] value];

   

   

[[cell textLabel] setText:companyName];

[[cell detailTextLabel] setText:webAddress];

Custom read operations

MBO Object API supports custom SQL queries that can be directly executed against the local database. Since there is no explicit relationship between sales order items and products defined, we use a custom SQL query to join the tables and retrieve the data to be displayed.

SUPQuery *query = [SUPQuery getInstance];

[query select:@"A.SO_ID,A.SO_ITEM_POS,B.PRODUCT_ID,B.CATEGORY,B.DESCRIPTION,B.SUPPLIER_NAME"];

[query from:@"SalesOrderItems" :@"A"];

[query join:@"Product" :@"B" :@"A.PRODUCT_ID" :@"B.PRODUCT_ID"];

SUPTestCriteria *criteria = query.testCriteria;

criteria = (SUPTestCriteria *)[[SUPAttributeTest match:@"A.SO_ID" :[self orderIDValue]] and:[SUPAttributeTest match:@"A.SO_ITEM_POS" :[self itemIDValue]]];

[query where:criteria];

SUPQueryResultSet *result = [CentralMBODesignCentralMBODesignDB executeQuery:query];


Unless there is a navigation property built between the 2 entitysets, we cannot use the $expand option in OData.  In this case, we may only be able to select attributes that are available in the Product entityset and not the attributes available in SalesOrderItems.

The above custom query can be translated in OData to read from the Products entityset using a PRODUCT_ID. This would return a single entity since PRODUCT_ID is a primary key.  So, Products entityset must implement the findByPrimaryKey method.

I have attached a sample application that reads data from the local database using the steps outlined above.  Please let me know if you have any questions.

Our next step in the migration process is to perform CUD operations against the local Offline Store.