Skip to content

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 OpResultobjects in two different roles - "as the parent" and "as children":

opresult-output-input.zoom80

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 the objectId property assigned by the server. You need to get the objectId 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":

opresultvaluereference-output-input.zoom80

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:

order-table

OrderItem table schema:

orderitem-table

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:

  1. Save an object in the Person table
  2. Gets the objectId value of the saved person object
  3. Stores the objectId in the CreationLog 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:

person-table-schema

CreationLog table schema:

creationlog-table

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 );
  }
} );