Skip to content

Additional Features

Offline OData provides some additional features.

A built-in function is provided to the $filter system query option to check for the existence of a related entity.

In the following example, Event has a relationship to GlobalTheme, and each event may or may not have a global theme.

You can use the following code to get all events that have an associated global theme:

DataQuery query = new DataQuery().filter(shareableQueryFunction.entityExists(EventDetail.theme.toPath()));
events = eventService.getEventDetails(query);
let query = DataQuery().filter(OfflineQueryFunction.entityExists(Event.theme.toPath()))
let events = try eventService.fetchEvents(matching: query)

To get all events without any associated global theme, use:

DataQuery query = new DataQuery().filter(shareableQueryFunction.entityExists(EventDetail.theme.toPath()).not());
events = eventService.getEventDetails(query);
let query = DataQuery().filter(OfflineQueryFunction.entityExists(Event.theme.toPath()).not())
let events = try eventService.fetchEvents(matching: query)

Prefer Header

Offline OData serves as an OData service that you can send a request to. When sending a GET request to query data from the offline store, you can add a Prefer header.

Currently, only odata.maxpagesize can be specified for the Prefer header:

odata.maxpagesize=<number greater than 0>

EventLog Entity Set

EventLog is a local-only entity set for the OfflineOData namespace that allows an offline application to view a log of past system events. This is a read-only entity set and any attempts to modify it will result in an error. Only the most recent 100 events are available from the log.

The following types of system events are logged:

  • Download Records a download, has a Type of Download, and the Details property contains the defining query subset used in the download if it was a successful partial download, or null if it was a successful full download, or error detail if the download failed.

  • Upload Records an upload, has a Type of Upload, and the Details property is null if the upload is successful, or error detail if the upload failed.

// A portion of EventLog entity type properties
Properties: [
    {
        Name: "ID",
        Type: "Edm.Int64",
        IsNullable: "false"
    },
    {
        Name: "Type",
        Type: "Edm.String",
        IsNullable: "false",
        MaxLength: "128"
    },
    {
        Name: "Time",
        Type: "Edm.DateTimeOffset",
        IsNullable: "true"
    },
    {
        Name: "Details",
        Type: "Edm.String",
        IsNullable: "true",
        MaxLength: "2048"
    },
    ...
/// A portion of EventLog entity type properties
Properties: [
    {
        Name: "ID",
        Type: "Edm.Int64",
        IsNullable: "false",
    },
    {
        Name: "Type",
        Type: "Edm.String",
        IsNullable: "false",
        MaxLength: "128",
    },
    {
        Name: "Time",
        Type: "Edm.DateTimeOffset",
        IsNullable: "true",
    },
    {
        Name: "Details",
        Type: "Edm.String",
        IsNullable: "true",
        MaxLength: "2048",
    },
    ...

Example queries that can be made against this entity set using the dynamic API:

  • Retrieve the details of the last full download

    // Set up entity set, entity type, and properties for EventLog
    EntitySet eventLogSet = eventService.getEntitySet("EventLog");
    EntityType eventLogType = eventLogSet.getEntityType();
    
    // Structural properties
    Property idProp = eventLogType.getProperty("ID");
    Property typeProp = eventLogType.getProperty("Type");
    Property timeProp = eventLogType.getProperty("Time");
    Property detailProp = eventLogType.getProperty("Details");
    
    // Get last Full Download
    DataQuery query = new DataQuery().from(eventLogSet)
        .filter(typeProp.equal("Download").and(detailProp.isNull()))
        .orderBy(timeProp, SortOrder.DESCENDING)
        .top(1);
    
    EntityValueList eventLogs = eventService.executeQuery(query).getEntityList();
    EntityValue eventLog = eventLogs.get(0);
    long id = idProp.getLong(eventLog);
    String type = typeProp.getString(eventLog);
    DataValue timeValue = timeProp.getValue(eventLog);
    if (timeValue != null) {
        GlobalDateTime time = GlobalDateTime.castRequired(timeValue);
        // Use the time
        ...
    }
    
    /// Set up entity set, entity type, and properties for EventLog
    let eventLogSet: EntitySet = eventService.entitySet(withName: "EventLog")
    let eventLogType: EntityType = eventLogSet.entityType
    
    /// Structural properties
    let Property idProp = eventLogType.getProperty("ID")
    let Property typeProp = eventLogType.getProperty("Type")
    let Property timeProp = eventLogType.getProperty("Time")
    let Property detailProp = eventLogType.getProperty("Details")
    
    /// Get last Full Download
    let query = DataQuery().from(eventLogSet)
        .filter(typeProp.equal("Download").and(detailProp.isNull()))
        .orderBy(timeProp, SortOrder.descending)
        .top(1)
    
    let eventLogs = try eventService.executeQuery(query).EntityList()
    let eventLog = eventLogs[0]
    let id = idProp.longValue(from: eventLog);
    let type = typeProp.stringValue(from: eventLog)
    let timeValue = timeProp.dataValue(from: eventLog)
    if timeValue != nil {
        let time = GlobalDateTime.castRequired(timeValue)
        /// Use the time
        ...
    }
    
  • Retrieve details of the last download for a particular defining query

    DataQuery query = new DataQuery().from(eventLogSet)
        .filter(typeProp.equal("Download")
        .and(detailProp.isNull().or(detailProp.indexOf("DefQ1").greaterEqual(0))))
        .orderBy(timeProp, SortOrder.DESCENDING)
        .top(1);
    
    eventLogs = eventService.executeQuery(query).getEntityList();
    
    let query = DataQuery().from(eventLogSet)
        .filter(typeProp.equal("Download")
        .and(detailProp.isNull().or(detailProp.indexOf("DefQ1").greaterEqual(0))))
        .orderBy(timeProp, SortOrder.descending)
        .top(1)
    
    let eventLogs = try eventService.executeQuery(query).entityList()
    
  • Retrieve times of the last 10 uploads

    DataQuery query = new DataQuery().from(eventLogSet)
        .select(timeProp)
        .filter(typeProp.equal("Upload"))
        .orderBy(timeProp, SortOrder.DESCENDING)
        .top(10);
    
    eventLogs = eventService.executeQuery(query).getEntityList();
    
    let query = DataQuery().from(eventLogSet)
        .select(timeProp)
        .filter(typeProp.equal("Upload"))
        .orderBy(timeProp, SortOrder.descending)
        .top(10)
    
    let eventLogs = try eventService.executeQuery(query).entityList()
    

Besides the dynamic API, EventLog is also available through a built-in proxy class OfflineODataEvent. The example below shows how to access EventLog with the proxy class:

// Get entities from the EventLog entity set
List<OfflineODataEvent> events = provider.getEventLog();

// Access each event
for (OfflineODataEvent event : events) {
    long id = entity.getId();
    String details = entity.getDetails();

    // Use the values
    ...
}
/// Get EventLog entities. The type of the result is Array<OfflineODataEvent>
let eventLogs = try eventService.fetchEventLog()

/// Examine the events
for event in eventLogs {
    let eventId = event.id
    let eventType = event.eventType
    ...
}

Note that property names from the proxy class are slightly different from what you use in the dynamic API. For example, when using the dynamic API, you can access a property named ID. When using the proxy class, the property name is id.

Lambda Operators

Offline OData supports two OData Version 4.0 lambda operators, any and all. Lambda operators allow an arbitrary Boolean expression to be evaluated on a related collection. That is, lambda operators allow the results of a query to be filtered based on a collection of related entities.

Both lambda operators are prefixed with a navigation property path that identifies a collection.

The arguments of a lambda operator are a lambda variable name followed by a colon (:) and a Boolean expression that uses the lambda variable name to refer to a property of the related entity type identified by the navigation path.

The any operator returns true if the Boolean expression evaluates to true for any member of the collection. The following example returns all events that have at least one session whose title contains "OData":

DataPath d = DataPath.lambda("d");
DataQuery query = new DataQuery().from(EventServiceMetadata.EntitySets.events)
    .filter(Event.sessions.any(d, d.path(Session.title).contains("OData")));
events = eventService.getEventDetails(query);
let d = DataPath.lambda("d")
let query = DataQuery().from(EventServiceMetadata.EntitySets.events)
    .filter(Event.sessions.any(d, d.path(Session.title).contains("OData")))
let events = try eventService.fetchEvents(matching: query)

The all operator returns true if the Boolean expression evaluates to true for all members of the collection. The following example returns all events that have session titles containing "OData":

DataQuery query = new DataQuery().from(EventServiceMetadata.EntitySets.events)
    .filter(Event.sessions.all(d, d.path(Session.title).contains("OData")));
events = eventService.getEventDetails(query);
let query = DataQuery().from(EventServiceMetadata.EntitySets.events)
    .filter(Event.sessions.all(d, d.path(Session.title).contains("OData")))
let events = try eventService.fetchEvents(matching: query)

Note

The all operator returns true if the collection is empty. For example, if an event has no session, it will satisfy the query above (because none of its sessions violates the criteria).

Request Queue Status

The offline store provides request queue status using the OfflineODataProvider.isRequestQueueEmpty() method. The method checks whether there are any pending requests stored in the request queue that have not yet been uploaded.

Request Queue Optimization

Request queue optimization is an algorithm that runs prior to an upload to reduce the number of requests that get sent to the back end while still maintaining the final state of the data as if all the requests the application made were sent as is.

The algorithm is turned off by default. To turn it on, before opening an offline store: In Java, call setEnableRequestQueueOptimization(true) on an OfflineODataParameters instance. In Swift, set the enableRequestQueueOptimization property of an OfflineODataParameters instance to true. Then open the offline store with the given parameters.

For example, say the application creates a new entity and then updates that entity several times before uploading. With the algorithm turned off, the create and all update requests will be sent to the back end in the next upload. However, if the algorithm was turned on, the create and update requests will be combined and only one create request will be sent in the next upload.

Consider another simple example. Say the application creates a new entity and then deletes that entity before doing the next upload. With the algorithm turned off, both the create and delete requests will be sent to the back end. However, if the algorithm was turned on, neither request will be sent.

Transaction Boundaries in Request Queue Optimization

The request queue optimization algorithm will maintain transaction (OData change set) boundaries. That is, requests within a transaction can be combined, and requests between the end of one transaction and start of the next transaction that reference the same entity can be combined, but the algorithm will not remove a request from within a transaction to combine with a request outside that transaction nor move a request from outside a transaction to combine with a request inside that transaction. In this context, the transaction could have been created manually by creating a local change set or built as a result of enabling transaction builder.

For example, consider the following sequence of requests:

  1. Create Customer 1 with Name="John"
  2. Batch #1:
    1. Change Set #1:
      1. Create Customer 2 with Name="Jan"
      2. Update Customer 2 with Name="Jane"
  3. Update Customer 1 with Name="John Doe"
  4. Update Customer 2 with Name="Jane Do"
  5. Update Customer 2 with Name="Jane Doe"

After the requests queue optimization algorithm is run, the requests will be:

  1. Create Customer 1 with Name="John Doe"
  2. Batch #1:
    1. Change Set #1:
      1. Create Customer 2 with Name="Jane"
  3. Update Customer 2 with Name="Jane Doe"

Keeping Requests As Is

Certain requests may need to be sent as-is. For example, imagine an entity which needs to move from one explicit state to another, such as NEW, IN PROCESS, SOLUTION PROVIDED, CONFIRMED. For such requests, use an OfflineODataRequestOptions instance and set the unmodifiableRequest property to true, then use the instance when executing the request. Such requests will be left as-is (not combined with other requests) when the request queue optimization algorithm is run.

Undoing Local Creation

This functionality supports the case of deleting an entity that was created locally but not yet uploaded undoing the local creation. All update requests in between the create and delete requests are also removed.

For example, say you have created a new entity (customer101) locally, updated it once, and then deleted it. After performing these operations, you invoke upload(). Below is the operation sequence:

  1. Request #1: Create customer101 locally
  2. Request #2: Update customer101
  3. Request #3: Delete customer101
  4. Invoke upload()

Note that before invoking upload(), customer101 is not available (since it has been deleted), but requests #1 to #3 are kept in the request queue.

If you've disabled undoing local creation, requests #1 to #3 will be sent to the back end when calling upload(). The entity customer101 will be created, updated, then deleted in the back end.

If the functionality is enabled, when calling upload(), the optimization removes requests #1 to #3 and they will therefore not be sent to the back end.

By default, this functionality is disabled. To enable it, before opening an offline store: In Java, call setEnableUndoLocalCreation(true) on a shareableParameters instance. In Swift, set the enableUndoLocalCreation property of a OfflineODataParameters instance to true. Then open the offline store with the given parameters.

This functionality differs from Request Queue Optimization. The request queue optimization algorithm respects transaction boundaries and unmodifiable requests. If undoing local creation is enabled, it does not matter whether the creates, updates, and deletes are in different transactions or are unmodifiable. It will remove all requests.

Transaction Builder

Transaction builder, when enabled, supports grouping requests into transactions (OData change sets) to be sent in the next upload that were not necessarily executed in a single change set initially. In other words, enabling this allows building large transactions for the next upload where all requests that need to go into the transactions are initially not known.

Which requests go in which transactions is controlled by specifying a TransactionID instance for an OfflineODataRequestOptions instance when performing the requests with the options instance. Requests that contain the same transaction ID will be put into the same transaction (see exceptional cases in Additional Notes below).

The functionality is disabled by default. To enable it, before opening an offline store: In Java, call setEnableTransactionBuilder(true) on an shareableParameters instance. In Swift, set enableTransactionBuilder property of an OfflineODataParameters instance to true. Then open the offline store with the given parameters.

For example, consider the following sequence of requests:

  1. Create Customer 1 with a transaction ID 1
  2. Create Order 1 with a transaction ID 1
  3. Create Product 1 with NO transaction ID
  4. Create Customer 2 with a transaction ID 2
  5. Update Customer 1 with a transaction ID 1
  6. Update Customer 2 with a transaction ID 2

Prior to executing the upload, the transaction builder algorithm will run and these requests will result in the following requests being sent to the back end (in this example assume that Request Queue Optimization is turned off):

  1. Batch #1
    1. Change Set #1
      1. Create Customer 1
      2. Create Order 1
      3. Update Customer 1
  2. Create Product 1
  3. Batch #2
    1. Change Set #1
      1. Create Customer 2
      2. Update Customer 2

The sample below shows how to specify the transaction ID for a request:

// Declare offline specific options
OfflineODataRequestOptions options = new OfflineODataRequestOptions();
options.setTransactionID(new TransactionID("1"));

// Declare a new Track entity
Track track = ...;

// Set property values
...

// Create the new track with the given transaction ID
eventService.createEntity(track, HttpHeaders.empty, options);
/// Declare offline specific request options
let options = OfflineODataRequestOptions()
options.transactionID = try TransactionID(stringLiteral: "1")

/// Declare a new track entity
let track = ...

/// Set property values
...

/// Create the new track with the given transaction ID
try eventService.createEntity(track, options: options)

Additional Notes

Transaction Builder can be used in combination with Request Queue Optimization.

To ensure referential integrity of related entities and to ensure all requests are valid OData requests, it is not always possible to group all requests with the same transaction ID into a single change set. Therefore, the transaction builder will group requests with the same transaction ID into as few change sets as possible.

In addition to referential integrity, unmodifiable requests can affect the transaction builder. Only one unmodifiable request per entity will be put into a single change set.

Undoing Pending Changes

While you can make changes to existing entities and new entities locally, you can also undo the pending changes without uploading them by: In Java, calling DataService.undoPendingChanges() for an EntityValue object. In Swift, calling shareableProvider.undoPendingChanges() for an EntityValue object.

An existing entity will be restored to the original status as if no changes had been made. A new entity will be removed as if the entity had never been created.

For example, let's say you have an existing entity customer101. You can either:

  • Patch customer101 several times. Undoing the changes restores all property values.
  • Associate customer101 to some purchase orders. Undoing the changes removes the relationships.
  • Delete customer101. Undoing this change will restore the entity.

As another example, say you have created a new entity (customer102) locally. No matter what subsequent operations you apply to customer102, undoing the changes will remove this entity as if it had never been created.

Offline OData also provides flexible support for more complicated cases. For example, let's say you created a new entity (order102) that deep inserted a new related entity customer102 in one request. There can be different sequences of performing undo:

  • Undo changes for customer102 first, customer102 will be removed, and order102 remains (since you are not undoing changes for order102) but is not related to any customer (since customer102 has been removed). The original request for creating order102 and customer102 will be adjusted accordingly to produce the correct result.

    You can continue to undo changes for order102, which removes it.

  • Undo changes for order102 first, order102 will be removed, and customer102 remains (since you are not undoing changes for customer102) but is not related to any order (since order102 has been removed). The original request for creating order102 and customer102 is adjusted accordingly to produce the correct result.

    You can continue to undo changes for customer102, which removes it.

Besides undoing pending changes for an EntityValue object as described above, the undoPendingChanges() API accepts the following alternative parameters:

  • An array of EntityValue objects

    Being able to undo an array of entities in a single batch reduces the time to accomplish this task and may also improve performance.

  • An HTTP header name and HTTP header value

    When you perform local modifications, you can set custom HTTP headers for each modification operation. You can then use the custom HTTP header later to undo the local change, as necessary. For example, consider the following sequence of requests:

    1. Create Customer customer1 by setting the custom HTTP header to name name1 and value value1.
    2. Update Customer customer1 by setting the custom HTTP header to name name2 and value value2.
    3. Create Product product1 by setting the custom HTTP header to name name3 and value value3.
    4. Update Product product1 by setting the custom HTTP header to name name3 and value value3.

    If you perform an undo using header name name2 and value value2, only the change in the second step will be reverted.

    If perform an undo using header name name3 and value value3, the changes in both the third step and the fourth step will be reverted.

  • An UploadCategory

    Calling undoPendingChanges() with an UploadCategory will revert all the changes in that UploadCategory. Only an UploadCategoryType of stringLiteral is supported for the undo operation.

  • A TransactionID

    Calling undoPendingChanges() with a TransactionID will revert all the changes in that TransactionID. Only a TransactionIDType of stringLiteral is supported for the undo operation.

Note that, if you delete an entity locally, a record of the local pending change is generated. If the local change has not yet been uploaded to the server, performing an undo operation will revert the deletion of the entity.

Undoing pending changes and undoing local creation are similar in terms of restoring the original status. The differences between are as follows:

  • Undoing local creation is an optimization for the back end to not send a POST request if a new entity created locally is deleted before it is uploaded. Undoing pending changes is not intended to be an optimization but rather allows correcting mistakes or changing your mind about what you did.
  • Undoing local creation only applies to new entities created locally. Undoing pending changes also applies to existing entities that have been downloaded from the back end.
  • For a new entity created locally, you can remove it by undoing pending changes for it without performing a deletion. However, you must perform a deletion on the entity if you want to apply undoing local creation in order to remove it.
  • Undoing local creation takes effect (removing affected requests from request queue) when you perform an upload. Undoing pending changes takes effect (removing affected requests from request queue) when you call the method.

Determining Modified/Created Relatives

You can retrieve information about related entities without using $expand. Specifically, you can determine whether an entity has one or more relatives that have any modification requests (including creates) unsent in the request queue and/or has one or more relatives that have locally applied requests (either requests that are in the request queue and unsent or requests which have been uploaded but not yet downloaded).

Relatives with Unsent Requests

To retrieve information about whether or not entities have relatives with unsent requests, indicating pending changes, in the request queue, add the custom query option sap.computeHasRelativesWithPendingChanges. The value of this custom query option is a comma separated list of navigation paths (that is, the same syntax as $expand). Only navigation paths specified in the query option will be used when computing if an entity has relatives with pending changes. Note that the sap.comupteHasRelativesWithPendingChanges option does not filter which entities are returned; it only enables computation of the hasRelativesWithPendingChanges property of EntityValue.

Relatives with Local Changes

To retrieve information about whether or not entities have relatives with local changes applied, add the custom query option sap.computeHasLocalRelatives. The value of this custom query option is a comma separated list of navigation paths (that is, the same syntax as $expand). Only navigation paths specified in the query option will be used when computing if an entity has relatives with local changes. Note that the sap.computeHasLocalRelatives option does not filter which entities are returned; it only enables computation of the hasLocalRelatives property of EntityValue.

Sample

The example below demonstrates building a query to retrieve all customers and will return information about whether or not the customers have related orders, products, or employers with local changes applied and also return information about whether or not the customers have related orders, products, or employers that have pending changes:

DataQuery query = new DataQuery().from(Customers)
    .custom("sap.computeHasLocalRelatives", "Orders/Products,Employer")
    .custom("sap.computeHasRelativesWithPendingChanges", "Orders/Products,Employer");
let query = DataQuery().from(Customers)
    .custom("sap.computeHasLocalRelatives", "Orders/Products,Employer")
    .custom("sap.computeHasRelativesWithPendingChanges", "Orders/Products,Employer")

OData V4 Containment Navigation Property

In OData V4, a containment navigation property represents a parent-child relationship where child entities can only exist if they are associated with a parent. Conceptually, you can think of containment as a strong ownership, the child entity cannot exist without its parent, and this, therefore, has some implications:

  • The URL path to access a contained child entity must include the parent entity.

For example, if "Orders" is a containment navigation property of "Customers", an Order entity would be accessed like this:

  GET /service/Customers(1)/Orders(100)
  • The contained (child) entity does not have its own entity set — it can only be accessed via the parent entity.
  • When you delete a parent entity, all its contained children entities are also deleted.

To declare a navigation property as a containment navigation property, the ContainsTarget keyword is set to true in the metadata. For example :

  <NavigationProperty Name="Orders" Type="Collection(Model.Order)" ContainsTarget="true" />

Offline OData now supports the OData V4 containment navigation property. This allows you to leverage the SDK for creating, updating, deleting, and querying Containment entities. The following is an example:

<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
  <edmx:DataServices>
    <Schema Namespace="Model" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityType Name="Customer">
        <Key>
          <PropertyRef Name="ID"/>
        </Key>
        <Property Name="ID" Type="Edm.Int32" Nullable="false"/>
        <Property Name="Name" Type="Edm.String"/>
        <NavigationProperty Name="Orders" Type="Collection(Model.Order)" ContainsTarget="true"/>
      </EntityType>
      <EntityType Name="Order">
        <Key><PropertyRef Name="ID"/></Key>
        <Property Name="ID" Type="Edm.Int32" Nullable="false"/>
        <Property Name="Title" Type="Edm.String"/>
      </EntityType>
      <EntityContainer Name="Container">
        <EntitySet Name="Customers" EntityType="Model.Customer">
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

You are then able to execute CRUD (Query, Create, Update, Delete) operations. See below for some sample code:

  • Query
    EntitySet  customersSet   = ds.getEntitySet( "Customers" );
    EntityType customerType  = customersSet.getEntityType();
    Property idProp = customerType.getProperty( "ID" );
    NavigationProperty ordersNavProp = (NavigationProperty)customerType.getProperty( "Orders" );

    DataPath dp_customerOrders = DataPath.of( ordersNavProp );
    DataQuery query = new DataQuery().from( customersSet ).withKey( new EntityKey().withProperty(idProp, IntValue.of( "10" )).path( dp_customerOrders ))
    ds.executeQuery(query);
    let customersSet: EntitySet = ds.entitySet(withName: "Customers")
    let customerType: EntityType = customersSet.entityType
    let idProp: Property = customerType.property(withName: "ID")
    let ordersNavProp : NavigationProperty = customerType.property( withName: "Orders" ) as! NavigationProperty

    var dp_customerOrders = DataPath.of( ordersNavProp )
    let query = DataQuery().from(customersSet).withKey(EntityKey().withProperty( idProp, value: IntValue.of( "10" )).path( dp_customerOrders )
    try ds.executeQuery( query )
  • Create
    EntitySet  customersSet   = ds.getEntitySet("Customers" );
    EntityType customerType  = customersSet.getEntityType();
    Property idProp = customerType.getProperty( "ID" );
    NavigationProperty ordersNavProp = (NavigationProperty)customerType.getProperty( "Orders" );
    EntityType orderType = ordersNavProp.getItemEntityType();

    EntityValue customer10 = ds.executeQuery( new DataQuery().from( customersSet ).withKey( new EntityKey().withProperty(idProp, IntValue.of( "10" ) ).getRequiredEntity();
    String[][]   newOrderValue =
            {
                    { "ID", "101" },
                    { "Title", "new Order" }
            };
    EntityValue newOrder = ds.makeEntity( orderType, newOrderValue );
    ds.createRelatedEntity( newOrder, customer10, ordersNavProp);
    let customersSet: EntitySet = ds.entitySet(withName: "Customers")
    let customerType: EntityType = customersSet.entityType
    let idProp: Property = customerType.property(withName: "ID")
    let ordersNavProp : NavigationProperty = customerType.property( withName: "Orders" ) as! NavigationProperty
    let orderType = ordersNavProp.itemEntityType

    let customer10 = try ds.executeQuery( DataQuery().from( customersSet ).withKey( EntityKey().withProperty( idProp, value: IntValue.of( "10" ) ).requiredEntity()
    let newOrder = EntityValue( orderType )
    //set propertites for newOrder
    ......
    try ds.createRelatedEntity( newOrder, in: customer10, property: ordersNavProp )
  • Update
    EntitySet  customersSet   = ds.getEntitySet("Customers" );
    EntityType customerType  = customersSet.getEntityType();
    Property idProp = customerType.getProperty( "ID" );
    NavigationProperty ordersNavProp = (NavigationProperty)customerType.getProperty( "Orders" );
    EntityType orderType = ordersNavProp.getItemEntityType();
    Property idOrderProp = orderType.getProperty( "ID" );
    Property titleOrderProp = orderType.getProperty( "Title" );

    DataPath dp_customerOrders = DataPath.of( ordersNavProp );
    dp_customerOrders = dp_customerOrders.withKey(new EntityKey().withProperty( idOrderProp, IntValue.of(101) ));
    DataQuery query = new DataQuery().from( customersSet ).withKey( new EntityKey().withProperty(idProp, IntValue.of( "10" )).path( dp_customerOrders ));
    EntityValue orderEntity = ds.executeQuery(query).getRequiredEntity();
    titleOrderProp.setString(orderEntity, "new Title" );
    ds.updateEntity( orderEntity );
    let customersSet: EntitySet = ds.entitySet(withName: "Customers")
    let customerType: EntityType = customersSet.entityType
    let idProp: Property = customerType.property(withName: "ID")
    let ordersNavProp : NavigationProperty = customerType.property( withName: "Orders" ) as! NavigationProperty
    let orderType = ordersNavProp.itemEntityType
    let idOrderProp: Property = orderType.property(withName: "ID")
    let titleOrderProp: Property = orderType.property(withName: "Title")

    var dp_customerOrders = DataPath.of( ordersNavProp )
    dp_customerOrders = dp_customerOrders.withKey(EntityKey().withProperty( idOrderProp, value: IntValue.of(101) ))
    let query = DataQuery().from(customersSet).withKey(EntityKey().withProperty( idProp, value: IntValue.of( "10" )).path( dp_customerOrders )
    let orderEntity = try ds.executeQuery( query ).requiredEntity()
    titleOrderProp.setStringValue(in: orderEntity, to: "new Title" )
    try ds.updateEntity( orderEntity )
  • Delete
    EntitySet  customersSet   = ds.getEntitySet("Customers" );
    EntityType customerType  = customersSet.getEntityType();
    Property idProp = customerType.getProperty( "ID" );
    NavigationProperty ordersNavProp = (NavigationProperty)customerType.getProperty( "Orders" );
    EntityType orderType = ordersNavProp.getItemEntityType();
    Property idOrderProp = orderType.getProperty( "ID" );

    DataPath dp_customerOrders = DataPath.of( ordersNavProp );
    dp_customerOrders = dp_customerOrders.withKey(new EntityKey().withProperty( idOrderProp, IntValue.of(101) ));
    DataQuery query = new DataQuery().from( customersSet ).withKey( new EntityKey().withProperty(idProp, IntValue.of( "10" ))
    .path( dp_customerOrders ));
    EntityValue orderEntity = ds.executeQuery(query).getRequiredEntity();
    ds.deleteEntity( orderEntity );
    let customersSet: EntitySet = ds.entitySet(withName: "Customers")
    let customerType: EntityType = customersSet.entityType
    let idProp: Property = customerType.property(withName: "ID")
    let ordersNavProp : NavigationProperty = customerType.property( withName: "Orders" ) as! NavigationProperty
    let orderType = ordersNavProp.itemEntityType
    let idOrderProp: Property = orderType.property(withName: "ID")

    var dp_customerOrders = DataPath.of( ordersNavProp )
    dp_customerOrders = dp_customerOrders.withKey(EntityKey().withProperty( idOrderProp, value: IntValue.of(101) ))
    let query = DataQuery().from(customersSet).withKey(EntityKey().withProperty( idProp, value: IntValue.of( "10" )).path( dp_customerOrders )
    let orderEntity = try ds.executeQuery( query ).requiredEntity()
    try ds.deleteEntity( orderEntity )

Last update: November 10, 2023