Skip to content

OData Operations

You can perform various operations on OData Entity Set such as retrieving data, binding data to UI controls, create, update, delete entities and call OData functions.

Retrieving Data

An entity set collection will be returned in the form of an array that can be bound to a control. You can use Target binding on controls that support it or you can use OData Read action or ClientAPI.read to retrieve the data.

Target Binding

You can use Target binding to bind data to the UI controls such as Object Table, Contact Table, and so on.

A Target binding typically consist of the following:

  • Service - refers to the target OData service
  • EntitySet - refers to the target OData Entity Set
  • QueryOptions - additional query parameters such as $expand, $filter, $orderby, $top, and more, that will be appended to the query to the OData service.

For certain container controls such as Object Table, we will enable paging (50 rows per page, by default), if it's the last control in the page. You can adjust the paging with DataPaging property of the control, if it's available.

You can also set the Target to a rule and return an array of objects with key-value properties.

Example

Binding an Object Table to the Events entity set:

{
  "ObjectCell": {
    "Title": "{Name} - {Type}",
    "Subhead": "{Country}",
    "Description": "{Description}",
    "Footnote": "{@odata.readLink}",
    "OnPress": "/EventApp/Actions/Events/NavToEventDetail.action"
  },
  "Target": {
    "EntitySet": "Events",
    "QueryOptions": "$orderby=Name",
    "Service": "/EventApp/Services/EventManagementService.service"
  },
  "_Type": "Section.Type.ObjectTable"
}

Binding an Object Table to the Events(123)/Sessions navigation property: Assuming your page current binding context is the data of Events(123)

{
  "Header": {
    "Caption": "Upcoming Session"
  },
  "ObjectCell": {
    "Title": "{Title}",
    "Description": "{Description}",
    "StatusText": "{Track/Name}",
    "Footnote": "Start: $(DT, {StartDateTime})",
  },
  "Target": {
    "EntitySet": "{@odata.readLink}/Sessions", //<-- will resolved to "EntitySet": "Events(123)/Sessions",
    "QueryOptions": "$orderby=StartDateTime&$top=3&$expand=Track",
    "Service": "/EventApp/Services/EventManagementService.service"
  },
  "Footer": {
    "Caption": "See All Sessions",
    "OnPress": "/EventApp/Actions/Sessions/NavToSessions.action"
  },
  "_Type": "Section.Type.ObjectTable"
}

Read Action & ClientAPI.read

You can use OData Read Action and ClientAPI.read to retrieve data from your OData service for business logic processing

OData Read Action Example

{
  "OnSuccess": "/EventApp/Rules/ProcessReadResult.js",
  "OnFailure": "/EventApp/Rules/TestRule.js",
  "Target": {
    "EntitySet": "Suppliers",
    "Service": "/EventApp/Services/EventManagementService.service"
  },
  "ActionResult": {
    "_Name": "ReadResult"
  },
  "_Type": "Action.Type.ODataService.Read"
}
export default function ProcessReadResult(clientAPI) {
  let res = clientAPI.getActionResult("ReadResult");
  let dataObservableArray = res.data;
  for (var i = 0; i < dataObservableArray.length; i++) {
    let item = dataObservableArray.getItem(0);
    // Process each item here...
  }
}

ClientAPI Read Example

export default function ProcessReadResult(clientAPI) {
  let servicePath = "/EventApp/Services/EventManagementService.service";
  let entitySet = "Events";
  let propertiesToSelect = ["EventID", "Name", "Description"]; //Optional - set to null to return all properties of the entity
  let queryOptions = "$orderby=Name";
  return clientAPI.read(servicePath, entitySet, propertiesToSelect, queryOptions).then((dataObservableArray)=> {
    for (var i = 0; i < dataObservableArray.length; i++) {
      let item = dataObservableArray.getItem(0);
      // Process each item here...
    }
    return null;
  });
}

Modifying Data

You can create, update and delete OData entities by using the OData Service actions such as CreateEntity, UpdateEntity and more.

All OData actions requires Target, the definition of the target depends on the actions you are performing.

For create entity you must target an entity set e.g. Events, but for update entity or delete entity, you must target a single specific entity which is targeted via read link e.g. Events(123) or using a filter that guarantee to return a single entity e.g. $filter=EventID eq 123

Create Entity

Example of Create Entity action:

{
  "_Type": "Action.Type.ODataService.CreateEntity",
  "Properties": {
    "Title": "{#Control:TitleSimplePropertyFormCell/#Value}",
    "StartDateTime": "{#Control:StartDateTimeFormCell/#Value}",
    "EndDateTime": "{#Control:EndDateTimeFormCell/#Value}",
    "Description": "{#Control:DescriptionNoteFormCell/#Value}"
  },
  "Target": {
    "EntitySet": "Sessions",
    "Service": "/EventApp/Services/EventManagementService.service"
  },
  "ActionResult": {
    "_Name": "CreateSessionResult"
  },
  "OnSuccess": "/EventApp/Actions/ClosePageAsCompleted.action",
  "OnFailure": "/EventApp/Actions/ActionFailedMessage.action"
}

Update Entity

Example of Update Entity action (take note of the ReadLink):

{
  "_Type": "Action.Type.ODataService.UpdateEntity",
  "Properties": {
    "Title": "{#Control:TitleSimplePropertyFormCell/#Value}",
    "Description": "{#Control:DescriptionNoteFormCell/#Value}"
  },
  "Target": {
    "EntitySet": "Sessions",
    "ReadLink": "{@odata.readLink}",
    "Service": "/EventApp/Services/EventManagementService.service"
  },
  "ActionResult": {
    "_Name": "UpdateSessionResult"
  },
  "OnSuccess": "/EventApp/Actions/ClosePageAsCompleted.action",
  "OnFailure": "/EventApp/Actions/ActionFailedMessage.action"
}

Delete Entity

Example of Delete Entity action (take note of the ReadLink and lacks of Properties):

{
  "_Type": "Action.Type.ODataService.DeleteEntity",
  "Target": {
    "EntitySet": "Sessions",
    "ReadLink": "{@odata.readLink}",
    "Service": "/EventApp/Services/EventManagementService.service"
  },
  "ActionResult": {
    "_Name": "DeleteSessionResult"
  },
  "OnSuccess": "/EventApp/Actions/ClosePageAsCompleted.action",
  "OnFailure": "/EventApp/Actions/ActionFailedMessage.action"
}

For more examples and information please see the Mobile Development Kit API Reference.

Linking

You can create link between item in several ways:

  1. Using CreateLinks in Create Entity action:

    In this example, you are creating an Sessions entity that link it to an existing Events entity via Session's "Event" navigation property:

    {
      "_Type": "Action.Type.ODataService.CreateEntity",
      "Properties": {
        "Title": "{#Control:TitleSimplePropertyFormCell/#Value}",
        "StartDateTime": "{#Control:StartDateTimeFormCell/#Value}",
        "EndDateTime": "{#Control:EndDateTimeFormCell/#Value}",
        "Description": "{#Control:DescriptionNoteFormCell/#Value}"
      },
      "Target": {
        "EntitySet": "Sessions",
        "Service": "/EventApp/Services/EventManagementService.service"
      },
      "CreateLinks": [
        {
          "Property": "Event",
          "Target": {
            "EntitySet": "Events",
            "ReadLink": "{#Page:EventDetail/#Property:@odata.readLink}",
          }
        }
      ],
      "ActionResult": {
        "_Name": "CreateSessionResult"
      },
      "OnSuccess": "/EventApp/Actions/ClosePageAsCompleted.action",
      "OnFailure": "/EventApp/Actions/ActionFailedMessage.action"
    }
    

    The above is equivalent of sending 2 POST requests: * 1st POST to create the Sessions Entity * 2nd POST request to link the created Sessions entity to the Events(123)

    POST https://my-odata-service.com/Sessions
    OData-Version: 2.0
    Content-Type: application/json;odata.metadata=minimal
    Accept: application/json
    
    {
    "Title": "Rapid Application Development with Mobile Development Kit",
    "StartDateTime": "20-10-2020T09:00:00Z",
    "EndDateTime": "20-10-2020T10:00:00Z",
    "Description": "Learning to build application rapidly with metadata driven application framework"
    }
    

    Let's assume the back end will generate ID 111 for the newly created session, hence Sessions(111)

    POST https://dummy-odata-service.com/Sessions(111)/Event/$ref
    OData-Version: 2.0
    Content-Type: application/json;odata.metadata=minimal
    Accept: application/json
    
    {
    "@odata.id": "https://dummy-odata-service.com/Events(123)"
    }
    
  2. Using CreateRelatedEntity action This action allow you to create an entity directly via the navigation property of a related parent entity.

    Example:

      {
        "_Type": "Action.Type.ODataService.CreateRelatedEntity",
        "Properties": {
          "Title": "{#Control:TitleSimplePropertyFormCell/#Value}",
          "StartDateTime": "{#Control:StartDateTimeFormCell/#Value}",
          "EndDateTime": "{#Control:EndDateTimeFormCell/#Value}",
          "Description": "{#Control:DescriptionNoteFormCell/#Value}"
        },
        "Target": {
          "EntitySet": "Sessions",
          "Service": "/EventApp/Services/EventManagementService.service"
        },
        "ParentLink":{
          "Property": "Session",
          "Target": {
            "EntitySet": "Events",
            "ReadLink": "{#Page:EventDetail/#Property:@odata.readLink}", // Assumption: EventDetail page's root binding context is an Events entity
          }
        },
        "ActionResult": {
          "_Name": "CreateSessionResult"
        },
        "OnSuccess": "/EventApp/Actions/ClosePageAsCompleted.action",
        "OnFailure": "/EventApp/Actions/ActionFailedMessage.action"
      }
    

    HTTP Request equivalent of the above example:

    Sending a post request against /Events(123)/Sessions navigation with payload of a session. This will create a new session and link it to Events(123) entity.

    POST https://dummy-odata-service.com/Events(123)/Sessions
    OData-Version: 2.0
    Content-Type: application/json;odata.metadata=minimal
    Accept: application/json
    
    {
     "Title": "Rapid Application Development with Mobile Development Kit",
    "StartDateTime": "20-10-2020T09:00:00Z",
    "EndDateTime": "20-10-2020T10:00:00Z",
    "Description": "Learning to build application rapidly with metadata driven application framework"
    }
    
  3. Deep Insert In this example, you will create an Events entity together with an associated GlobalThemes entity (in the Theme navigation property) using deep insert:

    {
      "_Type": "Action.Type.ODataService.CreateEntity",
      "Properties": {
       "Name": "{#Control:Name/#Value}",
       "Description": "{#Control:Description/#Value}",
       "Type": "{#Control:Type/#Value}",
       "Country": "{#Control:Country/#Value}",
       "Theme": {
         "DisplayName": "{#Control:ThemeDisplayName/#Value}",
         "ThemeCode": "{#Control:ThemeCode/#Value}"
       }
      },
      "Target": {
       "EntitySet": "Events",
       "Service": "/EventApp/Services/EventManagementService.service"
      },
      "ActionResult": {
       "_Name": "CreateEventResult"
      },
      "OnSuccess": "/EventApp/Actions/ClosePageAsCompleted.action",
      "OnFailure": "/EventApp/Actions/ActionFailedMessage.action"
    }
    

    The above is equivalent of sending a single POST request that will create an Events entity and an associated GlobalThemes entity in the same request.

    POST https://my-odata-service.com/Events
    OData-Version: 2.0
    Content-Type: application/json;odata.metadata=minimal
    Accept: application/json
    
    {
      "Name": "SAP Sapphire NOW",
      "Description": "Discover how you can transform your business and become an intelligent, sustainable enterprise at SAP Sapphire. Our reimagined event experience is being held as a hybrid series – a virtual event combined with a nine-city, in-person world tour experience.",
      "Type": "Public",
      "Country": "United States",
      "Theme": {
         "DisplayName": "SAP Horizon",
         "ThemeCode": "sap_horizon"
      }
    }
    

!!! Note There are some limitations with respect to deep insert in Offline OData. In the Offline OData scenario, deep insert only works for navigation properties with a maximum cardinality of one in the case where the back end cannot respond with child entities in the same order as the request. Please see Limitations with Deep Inserts for more information.

Call Function

You can use CallFunction action or callFunction API to execute and OData function (also known as Function Import and Action Import)

The OData Version 4 specification gives us a definition what Functions, Actions are:

Functions and Actions are operations that allow the execution of custom logic on parts of a data model. Functions are operations that do not have side effects and may support further composition, for example, with additional filter operations, functions or an action. Actions are operations that allow side effects, such as data modification, and cannot be further composed in order to avoid non-deterministic behavior. Actions and functions are either bound to a type, enabling them to be called as members of an instance of that type, or unbound, in which case they are called as static operations. Action imports and function imports enable unbound actions and functions to be called from the service root.

Example of using CallFunction action to call a function import

{
  "_Type": "Action.Type.ODataService.CallFunction",
  "Target": {
    "Function": {
      "Name": "getSessionAttendeesCount",
      "Parameters": {
        "SessionID": "{SessionID}"
      }
    },
    "ActionResult" : {
      "_Name" : "AttendeesCountResult"
    },
    "Service": "/EventApp/Services/EventManagementService.service",
    "OnSuccess": "/EventApp/Rules/DisplayCount.js",
    "OnFailure": "/EventApp/Actions/ActionFailedMessage.action"
  }
}
export default function DisplayCount(clientAPI) {
  let res = clientAPI.getActionResult("AttendeesCountResult");
  //Because this Function Import return an Edm.Int64 type data, the result in res.data will simply be an integer value.
  return clientAPI.executeAction({
    "Name": "/EventApp/Actions/Message.action",
    "Properties": {
      "Message": "Attendees Count: " + res.data
    }
  });
}

Example of using Call Function in the Target binding of an UI control

{
  "ObjectCell": {
     "Title": "{Title}",
     "Subhead": "{Description}",
     "StatusText": "{StartDateTime}",
     "SubstatusText": "{EndDateTime}"
  },
  "Target": {
    "Service": "/EventApp/Services/EventManagementService.service",
    "Function": {
      "Name": "getPopularSessions",
      "Parameters": {
        "EventID": "{EventID}"
      }
    }
  },
  "_Name": "PopularSessionTable",
  "_Type": "Section.Type.ObjectTable"
}

Example of using callFunction API to call a function import

export default function GetSessionAttendeesCount(clientAPI) {
  let servicePath = "/EventApp/Services/EventManagementService.service";
  let functionDef = {
    "Name": "getSessionAttendeesCount",
    "Parameters": {
      "SessionID": "1"
    }
  };
  let headers = null;
  return clientAPI.callFunction(servicePath, functionDef, headers).then((result)=>{
    //Because this Function Import return an Edm.Int64 type data, the result in result will simply be an integer value.
    return clientAPI.executeAction({
      "Name": "/EventApp/Actions/Message.action",
      "Properties": {
        "Message": "Attendees Count: " + result
      }
    });
  });

}

Last update: May 21, 2024