Skip to content

Handling Relationships in Offline OData

Overview

This document describes relationships, relationship cardinality, and various techniques for creating/updating/deleting relationships using the Offline OData API, which is based on the OData specification (up to version 4.0 with compatible version 2.0 features), and code samples that illustrate how Offline OData app developers can implement these techniques.

These techniques include:

  • Link operations
  • Bind operations
  • POST using a navigation property
  • Deep insert
  • Content ID referencing
  • Referential constraints

To better understand this document, readers (app developers) should have a working knowledge of:

  • OData
  • Offline OData
  • SAP Mobile Services or SAP Mobile Platform
  • Familiarity with SAP BTP SDK for iOS and Android SDK

Concept

Entities can have relationships between them, making them logically related to each other. For example, one customer can have multiple orders (one-to-many) and one order can belong to one customer (one-to-one). Relationships between one entity type (such as Customers) and another (such as Orders) are represented as navigation properties in OData. For example, using the Customers entity type's "Orders" navigation property, you can navigate from one customer entity to all related order entities.

Each end of a relationship has a cardinality (denoted by the edm: Multiplicity attribute in an association definition in the OData service metadata). The cardinality of an end of a relationship can be:

  • 0..1 (zero or one, means zero or one)
  • 1 (one, means exactly one)
  • * (many, means zero or more)

They can make six meaningful combinations:

  • zero or one to zero or one A person may have no ID card or has one and only one ID card.
  • one to zero or one A city has at most one mayor, but in some special cases a city may not have a mayor; a mayor must serve and only serve one city.
  • one to one A country must have one and only one capital city, and a capital city is the capital of one and only one country. This type of relationship is rare. The more common relationship types are one to zero or one and zero or one to zero or one.
  • one to many A man may have zero, one, or multiple children, and a child must have one and only one father.
  • many to zero or one In a company a computer may not have an owner (not assigned to any employee yet), and can be assigned to at most one employee, and an employee can have any number of computers.
  • many to many A supplier can provide multiple products, and a product can be provided by multiple suppliers; a supplier may not provide anything, and a product may not be provided by any supplier at a specific time.

Metadata

This section describes the entity types used in the samples. These entity types are also contained in the sample OData service. For example, there are two entity types, Customers and Orders, and a zero or one to many relationship between them (a customer may have any number of orders, and an order either does not belong to any customer or belongs to at most one customer), as follows: Customers (zero or oneend) ---------- Orders (manyend)

Customers Entity Type

The Customers entity type includes the CustomerID property as the key, Name property, and the Orders navigation property. The Customers entity type can expand to the Orders entity type via the "Orders" navigation property.

Sample Customer entity type in the OData metadata:

<EntityType Name="Customers">
    <Key>
        <PropertyRef Name="CustomerID"/>
    </Key>
    <Property Name="CustomerID" Type="Edm.Int32" Nullable="false"/>
    <Property Name="Name" Type="Edm.String" Nullable="false" MaxLength="20" Unicode="false" Collation="1252LATIN1"/>
    <NavigationProperty Name="Orders" Relationship="SAPSQLOData.Customer_Orders" FromRole="Customers_Principal" ToRole="Orders_Dependent"/>
</EntityType>

Orders Entity Type

The Orders entity type includes the OrderID property as the key, CustomerID property, and the Customer navigation property. The Orders entity type can expand to the Customers entity type via the "Customer" navigation property.

Sample Orders entity type:

<EntityType Name="Orders">
    <Key>
        <PropertyRef Name="OrderID"/>
    </Key>
    <Property Name="OrderID" Type="Edm.Int32" Nullable="false"/>
    <Property Name="CustomerID" Type="Edm.Int32" Nullable="true"/>
    <NavigationProperty Name="Customer" Relationship="SAPSQLOData.Customer_Orders" FromRole="Orders_Dependent" ToRole="Customers_Principal"/>
</EntityType>

Customers/Orders Entity Set

The sample OData service also includes the Customers entity set and the Orders entity set.

<EntitySet Name="Customers" EntityType="SAPSQLOData.Customers"/>
<EntitySet Name="Orders" EntityType="SAPSQLOData.Orders"/>

Customer_Orders Association

Referential constraint ensures that the entity being referenced (the principal entity) by the dependent entity always exists. For example, an Order entity either references nothing, or references an existing Customer entity. A referential constraint consists of the principal property (the key property of the entity type being referenced) and dependent property (the property of the dependent entity type used to reference the principal entity type). The dependent property is similar to the foreign key field of a table in a relational database referencing the key field of another table.

<Association Name="Customer_Orders">
    <End Role="Orders_Dependent" Type="SAPSQLOData.Orders" Multiplicity="*"/>
    <End Role="Customers_Principal" Type="SAPSQLOData.Customers" Multiplicity="0..1"/>
    <ReferentialConstraint>
        <Principal Role="Customers_Principal">
            <PropertyRef Name="CustomerID"/>
        </Principal>
        <Dependent Role="Orders_Dependent">
            <PropertyRef Name="CustomerID"/>
        </Dependent>
    </ReferentialConstraint>
</Association>

one to one Metadata

Sample metadata:

<EntityType Name="Owners">
    <Key>
        <PropertyRef Name="ID"/>
    </Key>
    <Property Name="ID" Type="Edm.Int32" Nullable="false"/>
    <Property Name="Name" Type="Edm.String" Nullable="false" MaxLength="20" Unicode="false" Collation="1252LATIN1"/>
    <NavigationProperty Name="Car" Relationship="SAPSQLOData.Owner_Car" FromRole="Owners_Principal" ToRole="Cars_Dependent"/>
</EntityType>
<EntityType Name="Cars">
    <Key>
        <PropertyRef Name="ID"/>
    </Key>
    <Property Name="ID" Type="Edm.Int32" Nullable="false"/>
    <Property Name="OwnerID" Type="Edm.Int32" Nullable="true"/>
    <NavigationProperty Name="Owner" Relationship="SAPSQLOData.Owner_Car" FromRole="Cars_Dependent" ToRole="Owners_Principal"/>
</EntityType>
<Association Name="Owner_Car">
    <End Role="Cars_Dependent" Type="SAPSQLOData.Cars" Multiplicity="1"/>
    <End Role="Owners_Principal" Type="SAPSQLOData.Owners" Multiplicity="1"/>
    <ReferentialConstraint>
        <Principal Role="Owners_Principal">
            <PropertyRef Name="ID"/>
        </Principal>
        <Dependent Role="Cars_Dependent">
            <PropertyRef Name="OwnerID"/>
        </Dependent>
    </ReferentialConstraint>
</Association>

Here is the full service metadata.

Relationships Operations

Creating Relationships

There are three scenarios for creating relationships:

  1. Create a relationship between two existing entities
  2. Create a relationship where one entity already exists
  3. Create a relationship where two entities don’t exist

Creating a Relationship Between Two Existing Entities

Declaration:

public void createLink(EntityValue from, Property property, EntityValue to, HttpHeaders headers, RequestOptions options)
open func createLink(from: EntityValue, property: Property, to: EntityValue, headers: HTTPHeaders = HTTPHeaders.empty, options: RequestOptions = RequestOptions.none) throws

Parameters:

Parameters Descriptions
from Source entity for the link to be created
property Source navigation property for the link to be created
to Target entity for the link to be created
headers Optional request-specific headers
options Optional request-specific options

The property parameter can be on a one end (for example Orders.Customer) or a many end (for example Customers.Orders).

Sample code:

public void createLinkExample() {
    Dataservice service = this.getService();
    Customer customer = service.getCustomer(new DataQuery().skip(1).top(1));
    Order odrder = service.getOrder(new DataQuery().top(1));
    service.createLink(customer, Customer.Orders, order);
}
let service = self.service
let customer = try service.fetchCustomer(matching: DataQuery().skip(1).top(1))
let order = try service.fetchOrder(matching: DataQuery().top(1))
try service.createLink(from: customer, property: customer.orders, to: order)

Declaration:

public void updateLink (EntityValue from, Property property, EntityValue to, HttpHeaders headers, RequestOptions options)
open func updateLink(from: EntityValue, property: Property, to: EntityValue, headers: HTTPHeaders = HTTPHeaders.empty, options: RequestOptions = RequestOptions.none) throws

Parameters:

Parameters Descriptions
from Source entity for the link to be updated
property Source navigation property for the link to be updated. This must be a one-to-one navigation property
to Target entity for the link to be updated
headers Optional request-specific headers
options Optional request-specific options

The property parameter can only be on one end (for example Orders.Customer) For example:

service.updateLink(order, Orders.Customer, customer);
try service.updatelink(from: order, property: order.customer, to: customer)

This assembles the following request:

Method URI
PUT EndPointUrl/Orders(1)/$links/Customer

Sample code:

public void updateLinkExample() {
    Dataservice service = this.getService();
    Customer customer = service.getCustomer(new DataQuery().top(1));
    Order order = service.getOrder(new DataQuery().skip(2).top(1));
    service.updateLink(order, Orders.Customer, customer);
}
let service = self.service
let customer = try service.fetchCustomer()
let order = try service.fetchOrder(matching: DataQuery().top(1))
try service.updatelink(from: order, property: Orders.customer, to: customer)
EntityValue.bindEntity (Two Entities Exist)

Bind an entity to a property of the current entity. This can be used before DataService.createEntity or DataService.updateEntity to create or update the bindings of navigation properties.

Declaration:

public void bindEntity(EntityValue entity, Property to)
open func bindEntity(_ entity: EntityValue, to: Property)

Parameters:

Parameters Descriptions
entity Entity to be bound
to Property the entity will be bound to

Sample code:

// Bind operations followed by update order/customer entity
public void bindEntityExample() {
    Dataserice service = this.getService();
    DataQuery customerQuery = new DataQuery().top(1).expand(Customer.Orders);
    Customer customer = service.getCustomer(customerQuery);
    DataQuery orderQuery = new DataQuery().top(1);
    Order order = service.getOrder(orderQuery);
    customer.bindEntity(order, Customer.Orders);
    service.updateEntity(customer);
}
// Bind operations followed by update order/customer entity
let service = self.service
let customerQuery = DataQuery().top(1).expand(Customer.orders)
let customer = try service.fetchCustomer(matching: customerQuery)
let orderQuery = DataQuery().top(1)
let order = try service.fetchOrder(matching: orderQuery)
customer.bindEntity(order, to: Customer.orders)
try service.updateEntity(customer)

// order.bindEntity(customer, to: order.customer)
// try service.updateEntity(order)
Create a Relationship with Referential Constraints

You can create a relationship between the two existing entities by updating the referential constraint (updating it from NULL to a specific value). Depending on the different types of referential constraint, we may use different APIs. Here we use Property.setInt as an example.

Declaration:

public void setInt(StructureBase target, int value)
open func setIntValue(in target: StructureBase, to value: Int)

Sample code:

DataService service = this.getService();
EntitySet orderEntitySet = service.getEntitySet("Orders");
EntityType orderEntityType = orderEntitySet.getEntityType();
Property customerIDProp = orderEntityType.getProperty("CustomerID");

Customer customer = service.getCustomer(new DataQuery().top(1));
Order order = service.getOrder(new DataQuery().skip(2).top(1));

customerIDProp.setInt(order, customer.getCustomerID())
service.updateEntity(order)
// Update with referential constraint
let service = self.service
let ordersESet = service.entitySet(withName: "Orders")
let ordersEType = ordersESet.entityType
let customerIDProp = ordersEType.property(withName: "CustomerID")

let customer = try service.fetchCustomer(matching: DataQuery().skip(1).top(1))
let order = try service.fetchOrder(matching: DataQuery().top(1))

customerIDProp.setIntValue(in: order, to: customerIDProp.intValue(from: customer))
try service.updateEntity(order)

Note

Until the most recent version of Offline OData, the relationship between two new entities created using this approach does not take effect immediately in your local store until the user performs a flush/upload and refresh/download with the offline store. Before the offline store gets synced, there is no real relationship in the local store, only a data item in the dependent entity.

Creating a Relationship Where One Entity Already Exists

If one entity already exists and the other entity needs to be created along with their relationship, we can use a Bind operation or Post using a navigation property to create the relationship.

DataService.createRelatedEntity

Create an entity in the target system, related to a parent entity via a parent navigation property.

Declaration:

public void createRelatedEntity(EntityValue entity, EntityValue parent, Property property, HttpHeaders headers, RequestOptions options)
open func createRelatedEntity(_ entity: EntityValue, in parent: EntityValue, property: Property, headers: HTTPHeaders = HTTPHeaders.empty, options: RequestOptions = RequestOptions.none) throws

Parameters:

Parameters Descriptions
entity Entity to be bound
parent Parent’s navigation property
property Parent’s navigation property
headers Optional request-specific headers
options Optional request-specific options

The property parameter can be a single-valued navigation property (for example Orders.Customer) or a collection-valued navigation property (for example Customer.Orders).

Sample code:

public void createRelatedEntityExample() {
    DataService service = this.getService();
    Customer customer = service.getCustomer(new DataQuery().top(1)
        .filter(Customer.customerID.equal("ALFKI")));
    List<Order> orders = service.getOrders(new DataQuery().top(1));
    Order newOrder = orders.get(0).copy();
    service.createRelatedEntity(newOrder, customer, Customer.Orders);
}
// Create an entity related to a parent entity via a parent navigation property.
let service = self.service
let customer = try service.fetchCustomer(matching: DataQuery().top(1)
    .filter(Customers.customerID.equal("ALFKI")))
let orders = try service.fetchOrders(matching: DataQuery().top(1))
let newOrder = orders.first!.copy()
try service.createRelatedEntity(newOrder, in: customer,
    property: Customers.Orders)
EntityValue.bindEntity (One Entity Exists)

Bind an entity to a property of the current entity. This can be used before DataService.createEntity or DataService.updateEntity to create or update the bindings of navigation properties.

Declaration:

public void bindEntity(EntityValue entity, Property to)
open func bindEntity(_ entity: EntityValue, to: Property)

Parameters:

Parameters Descriptions
entity Entity to be bound
to Property the entity will be bound to

to can be a single-valued navigation property (for example Orders.Customer) or a collection-valued navigation property (for example Customer.Orders)

Sample code:

public void bindEntityExample() {
    NorthwindService service = this.getService();

    Customer customer = new Customer();
    customer.setName("customernew");
    DataQuery orderQuery = new DataQuery().top(1);
    Order order = service.getOrder(orderQuery);
    customer.bindEntity(order, Customer.Orders);
    service.createEntity(customer)
}
// Bind and then create new customer entity
let service = self.service
let newCustomer = Customer()
try newCustomer.customerID = GuidValue.random().toString()
newCustomer.Name = "customernew"
let orderQuery = DataQuery().top(1)
let order = try service.fetchOrder(matching: orderQuery)
newCustomer.bindEntity(order, to: newCustomer.Orders)
try service.createEntity(newCustomer)
Creating a Relationship Referential Constraint when Creating a New Entity

Directly change the referential constraint if the referential constraint is maintained in the metadata.

Declaration:

public void setInt(StructureBase target, int value)
open func setIntValue(in target: StructureBase, to value: Int)

Sample code:

// Update with referential constraint when create a new entity
DataService service = this.getService();
EntitySet orderEntitySet = service.getEntitySet("Orders");
EntityType orderEntityType = orderEntitySet.getEntityType();
Property customerIDProp = orderEntityType.getProperty("CustomerID");

List<Order> orders = service.getOrders(query);
Order newOrder = orders.get(0).copy();
Customer customer = service.getCustomer(new DataQuery().skip(2).top(1));

customerIDProp.setInt(newOrder, newCustomer.getCustomerID())
service.createEntity(newOrder)
// Update with refrential constraint when create a new entity
let service = self.service
let ordersESet = service.entitySet(withName: "Orders")
let ordersEType = ordersESet.entityType
let customerIDProp = ordersEType.property(withName: "CustomerID")

let customer = try service.fetchCustomer(matching: DataQuery().skip(1).top(1))
let orders = try service.fetchOrders(matching: DataQuery().top(1))
let newOrder = orders.first!.copy()

customerIDProp.setIntValue(in: newOrder, to: customerIDProp.intValue(from: customer))
try service.createEntity(newOrder)

Note

Until the most recent version of Offline OData, the relationship between two new entities created using this approach does not take effect immediately in your local store until the user performs a flush/upload and refresh/download with the offline store. Before the offline store gets synced, there is no real relationship in the local store, only a data item in the dependent entity.

Create a Relationship Where Two Entities Don’t Exist

Add a pending created link to the change set. The link will be created when this change set is submitted. The relationship will be effective only after you do a flush/upload and refresh/download.

Declaration:

public void createLink(EntityValue from, Property property, EntityValue to, HttpHeaders headers, RequestOptions options)
open func createLink(from: EntityValue, property: Property, to: EntityValue, headers: HTTPHeaders = HTTPHeaders.empty, options: RequestOptions = RequestOptions.none)

Parameters:

Parameters Descriptions
from Source entity for the link to be created
property Source navigation property for the link to be created
to Target entity for the link to be created
headers Request-specific headers
options Request-specific options

Sample code:

public void applyChangesExample() {
    Dataservice service = this.getService();
    Customer customer = service.getCustomer(new DataQuery().top(1));
    Order order = service.getOrder(new DataQuery().top(1));
    Customer newCustomer = customer.copy();
    Order newOrder = order.copy();
    newCustomer.setName("NewCustomer");
    newOrder.setOrderID("LFKJ");

    ChangeSet change = new ChangeSet();
    change.createEntity(newCustomer);
    change.createEntity(newOrder);
    change.createLink(newCustomer, Customer.Orders, newOrder);
    service.applyChanges(change);
}
let service = self.service
let customers = try service.fetchCustomers(matching: DataQuery().top(1))
let newCustomer = customers.first!.copy()
let orders = try service.fetchOrders(matching: DataQuery().top(1))
let newOrder = orders.first!.copy()

let batch = RequestBatch()
let changes = ChangeSet()

changes.createEntity(newCustomer)
changes.createEntity(newOrder)
changes.createLink(from: newCustomer, property: Customers.Orders, to: newOrder)

batch.addChanges(changes)
try service.processBatch(batch)

Add a pending updated link to the change set. The link will be updated when this change set is submitted. The relationship will be effective only after you do a flush/upload and refresh/download.

Declaration:

public void updateLink(EntityValue from, Property property, EntityValue to, HttpHeaders headers, RequestOptions options)
open func updateLink(from: EntityValue, property: Property, to: EntityValue, headers: HTTPHeaders = HTTPHeaders.empty, options: RequestOptions = RequestOptions.none)

Parameters:

Parameters Descriptions
from Source entity for the link to be updated
property Source navigation property for the link to be updated. This must be a one-to-one navigation property.
to Target entity for the link to be updated
headers Request-specific headers
options Request-specific options

The property parameter can only be on one end (for example Orders.Customer)

Sample code:

public void applyChangesExample() {
    Dataservice service = this.getService();
    Customer customer = service.getCustomer(new DataQuery().top(1));
    Order order = service.getOrder(new DataQuery().top(1));
    Customer newCustomer = customer.copy();
    Order newOrder = order.copy();
    newCustomer.setName("NewCustomer");
    newOrder.setOrderID("LFKJ");

    ChangeSet change = new ChangeSet();
    change.createEntity(newCustomer);
    change.createEntity(newOrder);
    change.updateLink(newOrder, Orders.Customer, newCustomer);
    service.applyChanges(change);
}
// ChangeSet.updateLink
let service = self.service
let customers = try service.fetchCustomers(matching: DataQuery().top(2))
let newCustomer = customers.first!.copy()
let orders = try service.fetchOrders(matching: DataQuery().top(2))
let newOrder = orders.first!.copy()

let batch = RequestBatch()
let changes = ChangeSet()

changes.createEntity(newCustomer)
changes.createEntity(newOrder)
changes.updateLink(from: newOrder, property: Order.Customer, to: newCustomer)

batch.addChanges(changes)
try service.processBatch(batch)
Changeset.createRelatedEntity

Add a pending created entity to the change set, related to a parent entity via a parent navigation property. The entity will be created when this change set is submitted. The relationship will be effective only after you do a flush/upload and refresh/download.

Declaration:

public void createRelatedEntity(EntityValue entity, EntityValue parent, Property property, HttpHeaders headers, RequestOptions options)
open func createRelatedEntity(_ entity: EntityValue, in parent: EntityValue, property: Property, headers: HTTPHeaders = HTTPHeaders.empty, options: RequestOptions = RequestOptions.none)

Parameters:

Parameters Descriptions
entity Entity to be created
parent Previously created parent entity
property Parent’s navigation property
headers Request-specific headers
options Request-specific options

Sample code:

public void createRelatedEntityInChangeSetExample() {
    Dataservice service = this.getService();
    List<Customer> customers = service.getCustomers(new DataQuery()
        .filter(Customer.customerID.equal("ALFKI")));
    List<Order> orders = service.getOrders(new DataQuery().top(2));
    ChangeSet changes = new ChangeSet();
    Customer newCustomer = customers.get(0).copy();
    changes.createEntity(newCustomer);
    Order firstOrder = orders.get(0).copy();
    Order secondOrder = com.sap.cloud.mobile.odata.core.ListFunction.last(orders).copy();
    changes.createRelatedEntity(firstOrder, newCustomer, Customer.Orders);
    changes.createRelatedEntity(secondOrder, newCustomer, Customer.Orders);
    service.applyChanges(changes);
}
let service = self.service
let customers = try service.fetchCustomers(matching: DataQuery().top(1))
let newCustomer = customers.first!.copy()
let orders = try service.fetchOrders(matching: DataQuery().top(1))
let newOrder = orders.first!.copy()

let batch = RequestBatch()
let changes = ChangeSet()
changes.createRelatedEntity(newOrder, in: newCustomer, property: Customers.orders)

batch.addChanges(changes)
try service.processBatch(batch)
Property.setEntity (Deep Insert)

Deep insert is used to create new entities, as well as relationships between them, in a single request when the resource path is on the many end (COLLECTION). This approach is encapsulated in the Property.setEntityValue() API call as follows:

Declaration:

public void setEntity(StructureBase target, EntityValue value)
open func setEntityValue(in target: StructureBase, to value: EntityValue)

Sample code:

Dataservice service = this.getService();
Customer customer = service.getCustomer(new DataQuery().top(1));
Order order = service.getOrder(new DataQuery().top(1));
Customer newCustomer = customer.copy();
Order newOrder = order.copy();
newCustomer.setName("NewCustomer");
newOrder.setOrderID("LFKJ");

newOrder.setCustomer(newCustomer);
service.createEntity(newOrder);
let service = self.service
let customers = try service.fetchCustomers(matching: DataQuery().top(1))
let newCustomer = customers.first!.copy()
let orders = try service.fetchOrders(matching: DataQuery().top(1))
let newOrder = orders.first!.copy()

let ordersESet = try service.entitySet(withName: "Orders")
let orderToCustomer = ordersESet.entityType.property(withName: "Customer")
orderToCustomer.setEntityValue(in: newOrder, to: newCustomer)

try service.createEntity(newOrder)

Note

Deep insert TO many end is supported in OData with certain exceptions.

Deep insert TO many end is not fully supported by Offline OData because OData version 2.0 and version 4.0 do not provide a mechanism for the back-end server to provide entity IDs for the child entities. However, deep insert is supported in the case where your back-end server can respond with child entities in the same order as the request.

OData Version 4.01 will support this request by passing Content ID in the body and receiving mapping info as response from the OData server.

1:1 Relationship (Special Case)

The one to one relationship is a special case because it cannot be accomplished using the standard approaches (the three-step approach, consisting of: “create entity 1, create entity 2, relate them”; or the two-step approach: “create entity 1, create related entity 2”). These approaches do not work because the related entities, as well as the relationship, must be created simultaneously. However, using deep insert and batch, you can create a 1:1 relationship between entities.

Update Relationship

Existing relationships can be updated. In OData, you can use these four techniques to update relationships:

  • PUT through $links (DataService.updateLink)
  • PATCH through navigation property (Property.setEntityValue)
  • Update the referential constraint
  • Batch relationship operations using Content ID referencing

The following samples use Customers and Orders entity types and a zero or one to many relationship between them. Owners and Cars entity types with a one to one relationship between them are also used.

Updating a relationship changes how entities are related to each other. To illustrate relationship update, examine an example with three entities:

  • Customers(1)
  • Customers(2)
  • Orders(1)

There is a zero or one to many relationship between the Customers entity type and Orders entity type, meaning that a customer may have any number of orders, and an order may have no or at most one customer. A relationship betweenCustomers(1) andOrders(1) has already been established. The result is represented by View 1 as:

View 1: Customers(1)<===Orders(1) Customers(2)

When updating a relationship, at least one of the originally related entities remains in a relationship. For example, you can change the owner of Orders(1) from Customers(1) to Customers(2), therefore Orders(1) remains in a relationship, as represented in View 2:

View 2: Customers(1) Orders(1)===>Customers(2)

The relationship between Orders to Customers is many to zero or one. Therefore, from Orders(1)’s point of view, updating the relationship can also be viewed as replacing the customer related to it, for example, replacing Customers(1) that was related to it with Customers(2). The relationship between Customers to Orders is zero to one or many. From Customers(1)’s point of view, the update illustrated by View 2 actually removes Orders(1) from the set of orders that are related to it. From Customers(2)’s point of view, the update illustrated by View 2 actually adds Orders(1) into the set of orders that are related to it.

Declaration:

public void updateLink(EntityValue from, Property property, EntityValue to, HttpHeaders headers, RequestOptions options)
open func updateLink(from: EntityValue, property: Property, to: EntityValue, headers: HTTPHeaders = HTTPHeaders.empty, options: RequestOptions = RequestOptions.none) throws

Parameters:

Parameters Descriptions
from Source entity for the link to be updated
property Source navigation property for the link to be updated. This must be a one-to-one navigation property
to Target entity for the link to be updated
headers Request-specific headers
options Request-specific options

The property parameter can only be on one end (for example Orders.Customer) For example, both of the following code samples work:

public void updateLinkExample() {
    Dataservice service = this.getService();
    Customer customer = service.getCustomer(new DataQuery().top(1));
    Order order = service.getOrder(new DataQuery().top(1));
    service.updateLink(order, Orders.Customer, customer);
}
let service = self.service
let customer = try service.fetchCustomer()
let order = try service.fetchOrder(matching: DataQuery().top(1))
try service.updatelink(from: order, property: Orders.customer, to: customer)

This assembles the following request:

Method URI
PUT EndPointUrl/Orders(1)/$links/Customer

For example, there is an Orders(2) entity related to Customers(2), and another Customers(1) entity. You want to change the owner of Orders(2) to Customers(1).

Original relationship:

Customers(1) Orders(2)===>Customers(2)

Updated relationship:

Customers(1)<===Orders(2) Customers(2)

You can also use a batch request to update multiple relationships in a single batch. For example, there is an Orders(1) entity related to Customers(1), and another Orders(2) entity related to Customers(2). You want to switch the owners of both orders, i.e. change the owner of Orders(2) to Customers(1), and change the owner of Orders(1) to Customers(2).

Original relationship:

Orders(1)===>Customers(1) Orders(2)===>Customers(2)

Updated relationship:

Orders(1)===>Customers(2) Orders(2)===>Customers(1)

Sample Code:

public void updateLinkExample() {
    Dataservice service = this.getService();
    List<Customer> customers = service.getCustomer(new DataQuery().top(2));
    List<Order> orders = service.getOrder(new DataQuery().top(2));

    ChangeSet change = new ChangeSet();
    change.updateLink(orders[1], Orders.Customer, customers[2]);
    change.updateLink(orders[2], Orders.Customer, customers[1]);
    service.applyChanges(change);
}
let service = self.service
let customers = try service.fetchCustomers(matching: DataQuery().top(2))
let orders = try service.fetchOrders(matching: DataQuery().top(2))

let batch = RequestBatch()
let changes = ChangeSet()

changes.updateLink(from: orders[1], property: Orders.Customer, to: customers[2])
changes.updateLink(from: orders[2], property: Orders.Customer, to: customers[1])

batch.addChanges(changes)
try service.processBatch(batch)

Property.setEntity

Declaration:

public void setEntity(StructureBase target, EntityValue value)
open func setEntityValue(in target: StructureBase, to value: EntityValue)

For example, there is an Orders(2) entity related to Customers(2), and another Customers(1) entity . You want to change the owner of Orders(2) to Customers(1). Original relationship:

Customers(1) Orders(2)===>Customers(2)

Updated relationship:

Customers(1)<===Orders(2) Customers(2)

Sample code:

Dataservice service = this.getService();
Customer customer = service.getCustomer(new DataQuery().top(1));
Order order = service.getOrder(new DataQuery().top(1));

order.setCustomer(customer);
service.updateEntity(order);
let service = self.service
let customer = try service.fetchCustomers(matching: DataQuery().top(1))
let orders = try service.fetchOrders(matching: DataQuery().top(2))

let ordersESet = try service.entitySet(withName: "Orders")
let orderToCustomer = ordersESet.entityType.property(withName: "Customer")
orderToCustomer.setEntityValue(in: orders[2], to: customer)

Update a Relationship with Referential Constraint when Creating a New Entity

Depending on the different types of referential constraints, we may use different APIs. Here we use Property.setIntValue as an example.

Declaration:

public void setInt(StructureBase target, inwt value)
open func setIntValue(in target: StructureBase, to value: Int)

For example, there is an Orders(2) entity related to Customers(2), and another Customers(1) entity . You want to change the owner of Orders(2) to Customers(1).

Original relationship:

Customers(1) Orders(2)===>Customers(2)

Updated relationship:

Customers(1)<===Orders(2) Customers(2)

Sample Code:

Dataservice service = this.getService();
Customer customer = service.getCustomer(new DataQuery().top(1));
List<Order> orders = service.getOrders(new DataQuery().top(2));

orders[2].setCustomerID(customer.getCustomerID());
service.updateEntity(orders[2]);
let service = self.service
let ordersESet = service.entitySet(withName: "Orders")
let ordersEType = ordersESet.entityType
let customerIDProp = ordersEType.property(withName: "CustomerID")

let customer = try service.fetchCustomer(matching: DataQuery().skip(1).top(1))
let orders = try service.fetchOrder(matching: DataQuery().top(2))

customerIDProp.setIntValue(in: orders[2], to: customerIDProp.intValue(from: customer))
try service.updateEntity(orders[2])

Note

in the current Offline OData implementation, updating the relationship between these entities in this manner is ineffective locally, i.e., only the data item in the dependent entity is changed but the relationship is not yet updated. The user must flush/upload and refresh/download the offline store to make the relationship update take effect.

One to One Relationship (Special Case)

Updating multiple relationships in a batch is especially important for one to one relationships because batch update is the only way to update such relationships. When updating a one to one relationship, each end of the entity must be immediately related to another entity. For example, there is an Owners(1) entity that owns a Cars(1)entity and an Owners(2) entity that owns a Cars(2) entity. We can send a batch request that updates Owners(1) to Cars(2) and Owners(2) to Cars(1).

Original relationship:

Cars(1)===>Owners(1) Cars(2)===>Owners(2)

Updated relationship:

Cars(1)===>Owners(2) Cars(2)===>Owners(1)

Note

In this example, we have the Cars(1) entity related to Owners(1) and Cars(2) related to Owners(2), we can exchange the owners of the two cars as follows:

public void oneToOneExample() {
    Dataservice service = this.getService();
    List<Car> cars = service.getCars(new DataQuery().top(2));
    List<Owner> owners = service.getOwners(new DataQuery().top(2));

    ChangeSet change = new ChangeSet();
    change.updateLink(cars[1], Cars.owner, owners[2]);
    change.updateLink(cars[2], Cars.owner owners[1]);
    service.applyChanges(change);
}
let service = self.service
let cars = try service.fetchCars(matching: DataQuery().top(2))
let owners = try service.fetchOwners(matching: DataQuery().top(2))

let batch = RequestBatch()
let changes = ChangeSet()

changes.updateLink(from: cars[1], property: Cars.Owner, to: customers[2])
changes.updateLink(from: cars[2], property: Cars.Owner, to: customers[1])

batch.addChanges(changes)
try service.processBatch(batch)

Delete Relationship

Be aware that if one end of a relationship has a cardinality of one, such as one to one, one to zero or one and one to many, then the relationship cannot be deleted because the entity on the other end must always be related to an entity on the one end.

To remove a relationship between two entities Customers(1) and Orders(1), you can delete the link between them.

Declaration:

public void deleteLink(EntityValue from, Property property, EntityValue to, HttpHeaders headers, RequestOptions options)
open func deleteLink(from: EntityValue, property: Property, to: EntityValue = EntityValue.ofType(EntityType.undefined), headers: HTTPHeaders = HTTPHeaders.empty, options: RequestOptions = RequestOptions.none, completionHandler: @escaping (Error?) -> Void) -> Void

Parameters:

Parameters Descriptions
from Source entity for the link to be deleted
property Source navigation property for the link to be deleted
to Target entity for the link to be deleted. Can be omitted for a single-valued navigation property
headers Request-specific headers
options Request-specific options

Sample code:

public void deleteLinkExample2() {
    Dataservice service = this.getService();
    Customer customer = service.getCustomer(new DataQuery().skip(1).top(1));
    Order order = service.getOrder(new DataQuery().top(1));
    service.deleteLink(customer, Customer.Orders, order);
}
let service = self.service
let customer = try service.fetchCustomers(matching: DataQuery().skip(1).top(1))
let order = try service.fetchOrders(matching: DataQuery().top(1))
try service.deleteLink(from: customer, property: Customers.Orders, to: order)

Delete Relationship with Referential Constraints

When a referential constraint property is nullable, i.e., an entity with this property may relate to nothing, an existing relationship can be deleted by setting the property's value to nil. Depending on the different types of referential constraints, we may use different APIs. Here we use Property.setDataValue as an example.

For example, if there is an order entity related to a customer, we can update the order entity’s referential constraint property to nil as follows:

Sample code:

Dataservice service = this.getService();
Customer customer = service.getCustomer(new DataQuery().top(1));
Order order = service.getOrder(new DataQuery().top(1));
order.setCustomerID();
service.updateEntity(order);
let service = self.service
let ordersESet = service.entitySet(withName: "Orders")
let ordersEType = ordersESet.entityType
let customerIDProp = ordersEType.property(withName: "CustomerID")

let customer = try service.fetchCustomer(matching: DataQuery().skip(1).top(1))
let order = try service.fetchOrder(matching: DataQuery().top(1))

order.setDataValue(for: customerIDProp, to: nil)
try service.updateEntity(order)

Note

A user must also flush/upload and refresh/download the offline store to make the deletion take effect.

DataService.deleteEntity

Deleting a relationship typically means that the relationship is removed but the entities at both ends of the relationship remain. If you delete one of the entities, from the perspective of the other entity the relationship also disappears.

Declaration:

public void deleteEntity(EntityValue entity, HttpHeaders headers)
open func deleteEntity(_ entity: EntityValue, headers: HTTPHeaders = HTTPHeaders.empty, options: RequestOptions = RequestOptions.none) throws

Parameters:

Parameters Descriptions
entity Entity to be deleted
headers Request-specific headers
options Request-specific options

Sample code:

This approach is encapsulated in the DataService.deleteEntity() API call. For example, if there is an order entity related to a customer we can delete the entity as well as the relationship as follows:

// Delete entities
Dataservice service = this.getService();
Customer customer = service.getCustomer(new DataQuery().skip(1).top(1));
service.deleteEntity(customer)
let service = self.service
let customer = try service.fetchCustomers(matching: DataQuery().skip(1).top(1))
try service.deleteEntity(customer)

For example, if we want to remove the relationship of multiple Customer-order pairs, we can send a batch request with two DELETE requests in one change set. Then, add a pending deleted link to the change set. The link will be deleted when this change set is submitted.

Declaration:

public void deleteLink(EntityValue from, Property property, EntityValue to, HttpHeaders headers, RequestOptions options)
open func deleteLink(from: EntityValue, property: Property, to: EntityValue = EntityValue.ofType(EntityType.undefined), headers: HTTPHeaders = HTTPHeaders.empty, options: RequestOptions = RequestOptions.none)

Parameters:

Parameters Descriptions
from Source entity for the link to be deleted
property Source navigation property for the link to be deleted
to Target entity for the link to be deleted
headers Request-specific headers
options Request-specific options

Sample code:

public void deleteLinkExample() {
    Dataservice service = this.getService();
    List<Customer> customers = service.getCustomer(new DataQuery().top(2));
    List<Order> orders = service.getOrder(new DataQuery().top(2));

    ChangeSet change = new ChangeSet();
    change.deleteLink(orders[1], Orders.Customer, customers[2]);
    change.deleteLink(orders[2], Orders.Customer, customers[1]);
    service.applyChanges(change);
}
let service = self.service
let customers = try service.fetchCustomers(matching: DataQuery().top(2))
let orders = try service.fetchOrders(matching: DataQuery().top(2))

let batch = RequestBatch()
let changes = ChangeSet()

changes.deleteLink(from: orders[1], property: Orders.Customer, to: customers[2])
changes.deleteLink(from: orders[2], property: Orders.Customer, to: customers[1])

batch.addChanges(changes)
try service.processBatch(batch)

Delete one to one Relationship (Special Case)

In special cases, such as a one to one relationship, a relationship cannot be deleted on its own. Deleting the entity on either end also does not work. You must delete entities on both ends together, and the relationship is deleted as a result. You can use batch requests to accomplish this.

Sample code:

public void oneToOnedeleteLinkExample() {
    Dataservice service = this.getService();
    List<Car> cars = service.getCars(new DataQuery().top(2));
    List<Owner> owners = service.getOwners(new DataQuery().top(2));

    ChangeSet change = new ChangeSet();
    change.deleteLink(cars[1], Cars.owner, owners[2]);
    change.deleteLink(cars[2], Cars.owner owners[1]);
    service.applyChanges(change);
}
let service = self.service
let cars = try service.fetchCars(matching: DataQuery().top(2))
let owners = try service.fetchOwners(matching: DataQuery().top(2))

let batch = RequestBatch()
let changes = ChangeSet()

changes.deleteink(from: cars[1], property: Cars.Owner, to: customers[2])
changes.deleteLink(from: cars[2], property: Cars.Owner, to: customers[1])

batch.addChanges(changes)
try service.processBatch(batch)

Last update: September 23, 2022