Operation Result¶
One of the most powerful elements of the Backendless Transactions API is the ability to use the result of an operation in other subsequent operation within the same transaction. This is made possible by a "protocol" that all transactional operations follow: every operation returns an object representing the result which then can be used as the input in other subsequent operations. This allows for a "chaining effect" where data comes out of one operation and then fed into another.
Backendless uses a special class called OpResult
to represent the results of individual operations. Consider the example below. The example saves a new object in the database and then establishes a relationship between the created object and objects in another table. As you can see, the operation to save the object in the database returns OpResult
, which then is used in the subsequent operation to establish a relationship between the object and its children:
// create a UnitOfWork instance - it represents a transaction and
// will accumulate all operations in it.
UnitOfWork uow = new UnitOfWork();
// Create a person object
HashMap<String, Object> person = new HashMap<>();
person.put( "name", "Joe" );
person.put( "age", 20 );
// add a "create" operation to the transaction
// notice the result object will be reused later on
OpResult createPersonResult = uow.create( "Person", person );
// add an operation to create a relation. This operation uses
// the result from the previous "create" operation
uow.setRelation( createPersonResult, "visitedCountries", "name = 'China'" );
// execute the transaction. IMPORTANT - this is a blocking call
// when using on Android, make sure to use the non-blocking call
// to execute the transaction
UnitOfWorkResult result = uow.execute();
Log.i( "MYAPP" "Is transaction successful " + result.isSuccess() );
The most important in the example above is how the createPersonResult
object is used in the setRelation
operation.
It is important to understand that a OpResult
object represents different values depending on the operation that returned it. For instance, the Retrieving objects operation result is a collection of objects, while the Saving single object result is the created object. The table below summarizes the return values for all supported transaction operations in Backendless:
Operation |
Method name |
Result represented by OpResult when referenced in another operation |
---|---|---|
Retrieving objects |
find |
Collection of complete objects returned by the query |
Saving single object |
create |
Object saved on the database (with objectId assigned) |
Saving multiple objects |
bulkCreate |
Collection of objectId values |
Updating single object |
update |
Object updated in the database |
Updating multiple objects |
bulkUpdate |
Number of objects updated in the database |
Deleting single object |
delete |
Timestamp (as a number) when the object was deleted |
Deleting multiple objects |
bulkDelete |
Number of objects deleted in the database |
Setting a relation |
setRelation |
Number of child objects set in the relation to the parent |
Adding to a relation |
addToRelation |
Number of child objects added in the relation to the parent |
Deleting from a relation |
deleteRelation |
Number of child objects disconnected/removed from the relation with the parent |
Using OpResult as Input¶
Most operations can accept an OpResult
object as an argument. This is the how the "chaining effect" described above is accomplished. It is very important to understand that the value represented by an OpResult
object must be acceptable as an argument by the operation where it is being passed to. For example, the Deleting a single object operation can accept OpResult
. However, it cannot be any OpResult
, it must be a result which represents a single object. The diagram below illustrates where the output/result of an operation can be used as the input in another one. An arrow going out from a block represents an OpResult
object returned by that operation. As you can see some arrows have labels on them indicating the role OpResult
plays as the argument. This is the specifics of the relation management operations - they can accept OpResult
objects in two different roles - "as the parent" and "as children":
OpResultValueReference¶
Consider the following scenarios:
- In your transaction you run an operation to retrieve data from the database. The operation returns a collection of objects (represented by
OpResult
), however, you need to get the second object from the collection and update it in a subsequent operation. - In your transaction you save an object in the database. The operation returns the created object (represented by
OpResult
) which has theobjectId
property assigned by the server. You need to get theobjectId
value and store it in the database.
As you can see from these scenarios, there is a need to extract a value from OpResult
. In the first case the extracted value is an object from the collection. In the second one, it is a string value of the objectId
property. To accommodate these and many similar scenarios, the OpResult
class provides the following APIs:
// When the "opResultInstance" represents a collection of objects
// or strings, the API below extracts a single element from the
// collection. The element is obtained by its index within the
// collection with the "resultIndex argument. The index value
// is zero-based.
int resultIndex = some int value;
OpResultValueReference valueReference = opResultInstsance.resolveTo( resultIndex );
// When the "opResultInstance" represents a single object,
// the API below extracts the value of a property identified
// by the propName argument;
String propName = "some prop name";
OpResultValueReference valueReference = opResultInstance.resolveTo( propName );
// When the "opResultInstance" represents a collection of objects,
// the API below extracts the value of a property for an object
// at the specific instance. The index value is zero-based.
int resultIndex = some int value;
String propName = "some prop name";
OpResultValueReference valueReference = opResultInstsance.resolveTo( resultIndex,
propName );
The APIs return an instance of the OpResultValueReference
class. The class is used to represent a value within OpResult
. That value can be:
OpResultValueReference represents: |
API to get OpResultValueReference: |
OpResult from which OpResultValueReference obtained is |
Operation that returns OpResult |
---|---|---|---|
object |
opResultInstsance.resolveTo( resultIndex ); |
collection of objects |
Retrieving objects |
string |
opResultInstsance.resolveTo( resultIndex ); |
collection of strings |
Saving multiple objects |
property value |
opResultInstsance.resolveTo( propName ); |
object |
Saving single object, Updating single object |
property value |
opResultInstsance.resolveTo( resultIndex, propName ); |
collection of objects |
Retrieving objects |
The diagram below illustrates where in subsequent operations OpResultValueReference
can be as an input. An arrow indicates that it is possible to obtain an OpResultValueReference
object returned by the operation represented by the block where the arrow starts. An arrow coming into a block indicates that the block can accept OpResultValueReference
as an argument. As you can see some arrows have labels on them indicating the role plays as the argument. This is the specifics of the relation management operations - they can accept OpResultValueReference
objects in two different roles - "as the parent" and "as children":
OpResult Id¶
An OpResult
object can be treated as a reference to the return value of an operation which returned the object. Additionally, OpResult
maintains an identification of the operation which produced the result. That identification is done through OpResult Id. The ID can be obtained and assigned using the following APIs:
// get OpResult ID assigned by the SDK
String opResultId = opResultInstance.getOpResultID();
// assign a custom OpResult ID to OpResult
opResultInstance.setOpResult( "custom_OpResult_ID_value" )
Backendless uses the following scheme to generate operation ID values:
operation name + table where operation is performed + sequential number
For example, suppose your code adds the following operation to a transaction:
// create a UnitOfWork instance - it represents a transaction and
// will accumulate all operations in it.
UnitOfWork uow = new UnitOfWork();
// Create a person object
HashMap<String, Object> person = new HashMap<>();
person.put( "name", "Joe" );
person.put( "age", 20 );
// add a "create" operation to the transaction
// notice the result object will be reused later on
OpResult createPersonResult = uow.create( "Person", person );
String opResultID = createPersonResult.getOpResultId();
//
// skipped for brevity
//
the opResultId
variable in the code above will have a value of createPerson1
. The value of OpResult Id becomes important when you inspect transaction results. The final result of the entire transaction is an object of the UniOfWorkResult
class. You can read about it in the Unit Of Work section of this guide. The final result includes results of individual operations making up the transaction where each operation is referenced with the OpResult Id. When you use the setOpResultId
method described above, you change how the operation which returned the OpResult instance is identified. The name you assign will also be used to reference the operation result in the final UnitOfWorkResult
object.
Examples¶
Parent, Child and Relation¶
This example demonstrates how a single transaction can be used to accomplish the following:
- Save the parent object in the database
- Save the child objects in the database
- Create a relation between the parent and the children
It is important that the schema for both tables used in the example is created before the transaction is executed. This is what the schema looks like:
Order table schema:
OrderItem table schema:
UnitOfWork unitOfWork = new UnitOfWork();
// create the order object
HashMap<String, Object> orderObject = new HashMap<>();
orderObject.put( "orderId", "031820-CV1" );
orderObject.put( "amount", 189.20 );
// add the operation to save the order object in the database
OpResult createOrderResult = unitOfWork.create( "Order", orderObject );
// create order items
HashMap<String, Object> item1 = new HashMap<>();
item1.put( "name", "Paper Towels" );
item1.put( "quantity", 10 );
HashMap<String, Object> item2 = new HashMap<>();
item2.put( "name", "Bathroom Tissue" );
item2.put( "quantity", 20 );
List<Map<String, Object>> items = new ArrayList<Map<String, Object>>();
items.add( item1 );
items.add( item2 );
// add the operation to save order items in the database
OpResult createOrderItemsResult = unitOfWork.bulkCreate( "OrderItem", items );
// establish a relation between the order and order items
unitOfWork.setRelation( createOrderResult, "orderDetails", createOrderItemsResult );
// run the transaction
unitOfWork.execute( new AsyncCallback<UnitOfWorkResult>()
{
@Override
public void handleResponse( UnitOfWorkResult response )
{
System.out.printf( "Transaction completed - " + response );
}
@Override
public void handleFault( BackendlessFault fault )
{
System.out.println( "Server reported an error " + fault );
}
} );
Get a Property Reference from Operation Result¶
This example demonstrates how to store an object in a transaction and then access the value of a property of the saved object (its objectId
) to store it in another data table. In other words, the example does the following:
- Save an object in the
Person
table - Gets the
objectId
value of the saved person object - Stores the
objectId
in theCreationLog
table
It is important that the schema for both tables used in the example is created before the transaction is executed. This is what the schema looks like:
Person table schema:
CreationLog table schema:
UnitOfWork unitOfWork = new UnitOfWork();
// create a person object
Person person = new Person();
person.setName( "Batman" );
person.setAge( 36 );
// create the person object in the transaction
OpResult createPersonResult = unitOfWork.create( person );
// create an object for the log entry - log entry is another table
HashMap<String, Object> logEntry = new HashMap<>();
// get a reference to the "objectId" property for the created person
OpResultValueReference objectIdReference = createPersonResult.resolveTo( "objectId" );
// get the name of the table where person is saved
String tableName = createPersonResult.getTableName();
// populate the log entry object with data
logEntry.put( "objectCreated", objectIdReference );
logEntry.put( "tableName", tableName );
// create the log entry object in the transaction - it will
// be stored in a table called "CreationLog"
unitOfWork.create( "CreationLog", logEntry );
// execute the transaction on the server
unitOfWork.execute( new AsyncCallback<UnitOfWorkResult>()
{
@Override
public void handleResponse( UnitOfWorkResult response )
{
System.out.printf( "Transaction completed - " + response );
}
@Override
public void handleFault( BackendlessFault fault )
{
System.out.println( "Server reported an error " + fault );
}
} );