August 14, 2010

WSC SObject handling Lookup, Child relationships and XPath queries !

This post explains how to best handle/access  primitives, lookups and child relationships, when working with Partner-Sobject compiled from Salesforce WSC WSDL complier.

For ease of explaining, a sample SOQL query that will include both a lookup and a child-relationship over Contact SObject will be used. Here is the query

/**
 * SOQL for loading contact with some fields, lookup and child relationship
*/
static String SOQL = "Select FirstName, LastName, Name, Account.Name, AccountId , (Select Id, Title, Body From Notes), (Select Id, Priority, CreatedBy.Name, CreatedById From Cases) From Contact where AccountId  != null and Id = 'recordid goes here'";

In above SOQL, we are accessing

  • Direct fields “FirstName” and “LastName”
  • Name field on “Account” lookup
  • Child relationship “Notes”
  • Child relationship “Cases” with next level child “CreatedBy” for Case

Next, will be showed a small code snippets to access each of the above via WSC Sobject.

Assumption : Before going to code snippets. I am assuming, you have already loaded SObject via Partner Query from your Java code. If you want to know how to do so, click here for samples. So I am assuming following java variable is created and correctly initialized

// WSC partnerConnection created with your credentials
QueryResult query = partnerConnection.query(SOQL);
SObject conSobj = query.getRecords()[0];

SObject->getField(“<FieldName>”) the ease accessor

The getField(“FieldName”) method is very useful method to load any of the direct attribute, lookup or child relationships. It returns Object instance, that can typically be casted for getting the correct results.

Accessing Direct Fields from SObject

Its simple direct method Sobject->getField(“<FieldName>”); does that straight. This method returns Object, so cast it to String.

String firstName = (String) conSobj.getField("FirstName");
String lastName = (String) conSobj.getField("LastName");
System.out.println(" FirstName: " + firstName);
System.out.println(" LastName: " + lastName);

Accessing Lookups from Sobject

// Again getField() is called, but this time it will return an XmlObject
// that will be used later to fetch the account name
XmlObject account = (XmlObject) conSobj.getField("Account");
// XmlObject has  getField() method to fetch further
String accountName = (String) account.getField("Name");
System.out.println(" Account-Name: " + accountName);

Accessing Child Relation Ships from Sobject

Child relationships are different from lookups, as they can have more than 1 record, so accessing them becomes little tricky. Again Sobject->getField(“RelationShipName”) can be used to get handle over the bunch of items in the child-relation. But its tricky because with WSC SObject implementation, a couple of extra items are returned with actual child(Note/Case record). These extra items are fields like “done”, “queryLocator” and “size”.

So a couple of different approaches can be used to access child relationships i.e. Notes or Cases from Contact SObject.

Accessing “Notes” Code Snippet – Approach 1

XmlObject notes = (XmlObject) conSobj.getField("Notes");
Iterator<XmlObject> notesItr = notes.getChildren();
while (notesItr.hasNext()) {
   XmlObject nextChild = notesItr.next();
	// Only children with "records" contain the real Note records, 
	// so skip rest like done, queryLocator and size etc
	if (!nextChild.getName().getLocalPart().equals("records"))
		continue;
	System.out.println("Note Id : " + nextChild.getField("Id"));
	System.out.println("Note Title :" + nextChild.getField("Title"));
	System.out.println("Note Body :" + nextChild.getField("Body"));
}

Accessing “Notes” Code Snippet – Approach 2

This approach does the same using XPATH via SObject.evaluate(“XPATH”) method. Note we reduced if condition a bit. 

Iterator<XmlObject> children = conSobj.evaluate("Notes/records");
while (children.hasNext()) {
   // Note we don't need to skip anything, we got just what we need
   XmlObject nextChild = children.next();
   System.out.println("Note Id : " + nextChild.getField("Id"));
   System.out.println("Note Title :" + nextChild.getField("Title"));
   System.out.println("Note Body :" + nextChild.getField("Body"));
}

Accessing “Cases” and Case->CreatedBy User, Code Snippet – Approach 1

Iterator<XmlObject> children = conSobj.evaluate("Cases/records");
while (children.hasNext()) {
   XmlObject nextChild = children.next();   
   System.out.println(" CaseId " + nextChild.getField("Id"));
   System.out.println(" Case Priority :" + nextChild.getField("Priority"));
   // Note we again accessed the child(CreatedBy) of a child(Case)
   XmlObject caseCreator = (XmlObject) nextChild.getField("CreatedBy");
   // Access the name of second level child directly
   System.out.println(" CaseCreator " + caseCreator.getField("Name"));
}

XPaths for everything, Code Snippet – Approach 3

This approach shows how you can use XPATH, to dig into any level on an Sobject. For ex. xpath: "OpportunityContactRoles/records/Contact/LastName" this will list all LastNames.

// You can create Xpath to dig upto any level in Object graph. 
// So if you just need to Case Ids, its an easy xpath shortcut to that.
Iterator<XmlObject> allCaseIds = conSobj.evaluate("Cases/records/Id");
while (allCaseIds.hasNext()) {
   String caseId = (String) allCaseIds.next().getValue();
   System.out.println(" CaseId " + caseId);
}

WARNING about XPATH – SObject.evaluate() !

Using the SObject->evaluate() method canhurt performance of your application for large number of records in general or in child relation ships. This is because,as per current SObject code of evaluating xpath, its not caching the results i.e. all the object graph from root to leaf is scanned on every call.

Also, full xpath syntax is not supported yet on the SObject->evaluate() method. So path like Cases/records[1] etc will not work.