Additional Features¶
Offline OData provides some additional features.
Determining Existence of Related Entity¶
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 aType
ofDownload
, and theDetails
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 aType
ofUpload
, and theDetails
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:
- Create Customer 1 with Name="John"
- Batch #1:
- Change Set #1:
- Create Customer 2 with Name="Jan"
- Update Customer 2 with Name="Jane"
- Change Set #1:
- Update Customer 1 with Name="John Doe"
- Update Customer 2 with Name="Jane Do"
- Update Customer 2 with Name="Jane Doe"
After the requests queue optimization algorithm is run, the requests will be:
- Create Customer 1 with Name="John Doe"
- Batch #1:
- Change Set #1:
- Create Customer 2 with Name="Jane"
- Change Set #1:
- 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:
- Request #1: Create
customer101
locally - Request #2: Update
customer101
- Request #3: Delete
customer101
- 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:
- Create Customer 1 with a transaction ID
1
- Create Order 1 with a transaction ID
1
- Create Product 1 with NO transaction ID
- Create Customer 2 with a transaction ID
2
- Update Customer 1 with a transaction ID
1
- 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):
- Batch #1
- Change Set #1
- Create Customer 1
- Create Order 1
- Update Customer 1
- Change Set #1
- Create Product 1
- Batch #2
- Change Set #1
- Create Customer 2
- Update Customer 2
- Change Set #1
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, andorder102
remains (since you are not undoing changes fororder102
) but is not related to any customer (sincecustomer102
has been removed). The original request for creatingorder102
andcustomer102
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, andcustomer102
remains (since you are not undoing changes forcustomer102
) but is not related to any order (sinceorder102
has been removed). The original request for creatingorder102
andcustomer102
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
objectsBeing 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:
- Create Customer
customer1
by setting the custom HTTP header to namename1
and valuevalue1
. - Update Customer
customer1
by setting the custom HTTP header to namename2
and valuevalue2
. - Create Product
product1
by setting the custom HTTP header to namename3
and valuevalue3
. - Update Product
product1
by setting the custom HTTP header to namename3
and valuevalue3
.
If you perform an undo using header name
name2
and valuevalue2
, only the change in the second step will be reverted.If perform an undo using header name
name3
and valuevalue3
, the changes in both the third step and the fourth step will be reverted. - Create Customer
-
An
UploadCategory
Calling
undoPendingChanges()
with anUploadCategory
will revert all the changes in thatUploadCategory
. Only anUploadCategoryType
ofstringLiteral
is supported for the undo operation. -
A
TransactionID
Calling
undoPendingChanges()
with aTransactionID
will revert all the changes in thatTransactionID
. Only aTransactionIDType
ofstringLiteral
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.
Undo The Last Change¶
You can undo the last change made to the local database by deleting the last request in the request queue. This action is useful when you want to revert only the last change made to the offline store before uploading.
The following example shows how to undo the last change in the request queue:
try {
// query the last unsent request in the RequestQueue entity set
query = new DataQuery().from(OfflineODataMetadata.EntitySets.requestQueue).orderBy(OfflineODataRequest.requestID, SortOrder.DESCENDING)
.filter(QueryOperator.equal(OfflineODataRequest.status, StringValue.of("Unsent"))).top(1);
EntityValueList unsentRequests = dataService.executeQuery(query).getEntityList();
// undo the change by deleting the request
if (unsentRequests.length() > 0) {
EntityValue lastRequest = unsentRequests.first();
dataService.deleteEntity(lastRequest);
}
} catch (Exception e) {
Log.w(TAG, e);
}
try {
// query the last unsent request in the RequestQueue entity set
val query: DataQuery = DataQuery().from(OfflineODataMetadata.EntitySets.requestQueue)
.orderBy(OfflineODataRequest.requestID, SortOrder.DESCENDING).filter(
QueryOperator.equal(OfflineODataRequest.status, StringValue.of("Unsent"))
).top(1)
var unsentRequests: EntityValueList = dataService.executeQuery(query).getEntityList()
// undo the change by deleting the request
if (unsentRequests.length > 0) {
var lastRequest = unsentRequests.first()
dataService.deleteEntity(lastRequest)
}
} catch (e: Exception) {
Log.w(TAG, e)
}
do {
// query the last unsent request in the RequestQueue entity set
let query = DataQuery().from(OfflineODataMetadata.EntitySets.requestQueue).filter(QueryOperator.equal(OfflineODataRequest.status, StringValue.of("Unsent"))).orderBy(OfflineODataRequest.requestID, SortOrder.descending).top(1)
let unsentRequests = try dataService.executeQuery(query).entityList()
// undo the change by deleting the request
if unsentRequests.length > 0 {
let lastRequest = unsentRequests.first()
try dataService.deleteEntity(lastRequest)
}
} catch {
print("undo last change failed with error \(error)")
}
Note
ILOData command line utility supports reading and deleting requests from the RequestQueue
entity set. Below is an example of the ILOData command to delete a request from the request queue: DELETE RequestQueue(1)
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 )