Best Practices for Salesforce to Salesforce

Abstract

Salesforce to Salesforce is a Force.com feature that lets you configure two Force.com environments (orgs) so that they share data records in real time. This article describes several best practices to solve some of the complex salesforce to salesforce scenarios.

Introduction

In today's online world of business, it is a competitive advantage to have partners who can contribute to your business growth. While doing business with partners, it might be required to share business data with them. If both you and your partner use Salesforce.com, it is very easy to share data using Salesforce to Salesforce.

In most scenarios, data can be shared using the standard Salesforce.com user interface, however, there are some instances when it's better to automate sharing, including carrying over relationships between objects in the source org to the target org. This document provides tips & tricks with code samples, as well as best practices, to roll-out both simple and complex business processes with Salesforce to Salesforce.

What follows is a list of best practices - each is described by a challenge, and each has a solution provided in code. Feel free to skip to the best practices that best suite your needs.

In all of the best practices below, we use two orgs (environments):

  • the Force Corp org represents the source org (we'll typically be moving data from this org)
  • the ACME Corp org represents the target org (we'll typically be moving data to this org)

Here are the best practices described in this article:

Best Practice: Retrieving Connection IDs

Challenge: I would like to retrieve the connection ID for a given connection, instead of hard coding the connection ID.

Solution: You can use a helper class that queries the PartnerNetworkConnection object by name. Here's a class that does this:

public class ConnectionHelper {

    public static Id getConnectionId(String connectionName) {
    
        List<PartnerNetworkConnection> partnerNetConList =
           [Select id from PartnerNetworkConnection where connectionStatus = 'Accepted' and connectionName = :connectionName];
        
        if ( partnerNetConList.size() != 0 ) {
            return partnerNetConList.get(0).Id;
        }
        
        return null;
    }
    
    public static Id getConnectionOwnerId(String connectionName) {
    
        List<PartnerNetworkConnection> partnerNetConList = 
          [Select createdById from PartnerNetworkConnection where connectionStatus = 'Accepted' and connectionName = :connectionName];
        
        if ( partnerNetConList.size() != 0 ) {
            return partnerNetConList.get(0).createdById;
        }
        
        return null;
    }
}       

Now you simply need a call such as ConnectionHelper.getConnectionId('Acme Corp') to retrieve a connection ID.

Best Practice: Automatically share new accounts

Challenge: When new accounts or contacts are created in my org, I would like to share them automatically with another org.

Solution: You can push records from one org to another org using the PartnerNetworkRecordConnection object.

Here is the sample code you can use as a trigger on the Contact object.

trigger ContactTrigger on Contact (after insert) { 
  
    // Define connection id 
    Id networkId = ConnectionHelper.getConnectionId('Acme Corp'); 
    
    Set<Id> localContactAccountSet = new Set<Id>(); 
    List<Contact> localContacts = new List<Contact>(); 
    Set<Id> sharedAccountSet = new Set<Id>();    
    
    // only share records created in this org, do not add contacts received from another org. 
    for (Contact newContact : TRIGGER.new) { 
        if (newContact.ConnectionReceivedId == null && newContact.AccountId != null) { 
            localContactAccountSet.add(newContact.AccountId); 
            localContacts.add(newContact); 
        }         
    } 
    
    if (localContactAccountSet.size() > 0) { 
        // Get the contact account's partner network record connections 
        for (PartnerNetworkRecordConnection accountSharingRecord :  
                                  [SELECT p.Status, p.LocalRecordId, p.ConnectionId 
                                   FROM PartnerNetworkRecordConnection p              
                                   WHERE p.LocalRecordId IN :localContactAccountSet]) { 
                  
            // for each partner connection record for contact's account, check if it is active 
            if ((accountSharingRecord.status.equalsignorecase('Sent') 
              || accountSharingRecord.status.equalsignorecase('Received')) 
              && (accountSharingRecord.ConnectionId == networkId)) { 
                sharedAccountSet.add(accountSharingRecord.LocalRecordId); 
            }               
        } 
            
        if (sharedAccountSet.size() > 0) { 
            List<PartnerNetworkRecordConnection> contactConnections =  new  List<PartnerNetworkRecordConnection>(); 
            
            for (Contact newContact : localContacts) { 
  
                if (sharedAccountSet.contains(newContact.AccountId)) { 
                   
                    PartnerNetworkRecordConnection newConnection = 
                      new PartnerNetworkRecordConnection( 
                          ConnectionId = networkId, 
                          LocalRecordId = newContact.Id, 
                          SendClosedTasks = false, 
                          SendOpenTasks = false, 
                          SendEmails = false, 
                          ParentRecordId = newContact.AccountId); 
                          
                    contactConnections.add(newConnection); 
                
                } 
            } 
  
            if (contactConnections.size() > 0 ) { 
                   database.insert(contactConnections); 
            } 
        } 
    } 
}                   

Best Practice: Reestablishing lookup relationships

Challenge: There are two lookup fields on an object in the source org and you would like to share the object, along with the lookup objects, and reestablish the lookup relationship with the target org.

Solution: Generally, it is easier to set a lookup field as parentrecordid if there is only one lookup, but when you have two lookup fields, you cannot use parentrecordid.

The prerequisite for this solution is that both referenced records (via lookup) are already externally shared via Salesforce to Salesforce (S2S) with the target org.

Publish the second lookup object's ID from the source org using a formula field and subscribe it into a custom text field with size 100 in the target org object.

Once you have the lookup ID available in the target org, find out the local record ID corresponding to the lookup ID using PartnerNetworkRecordConnection, and populate the second lookup using the queried localrecordid corresponding to partnerrecordid.

Here's an example: The Pyramid construction account has two contacts: Pat and Joe. Pat reports to Joe (i.e. the Reports To field on Pat's record has a lookup to Joe). The Contact record is shared from Force Corp to ACME.

Force Corp Org (Source Org) Setup

Define a new custom formula field on Force Corp's contact object to make the Reports To ID visible to Salesforce to Salesforce. Call the new field ReportsToIdFormula, give it a type Formula, Formula: ReportsToId.

Publish this new custom formula field on the Contact object.

ACME Corp Org (Target Org) Setup:

Define a new custom field on Acme Corp's Contact object to receive the Reports To ID, New field name: Remote Reports To ID, API Name: Remote_Reports_To_ID__c, Data Type: Text (100)

Go to the Connections tab, click Edit against Contact in the Subscribe/Unsubscribe section and map the Reports To field to the new custom field (ensure that the Reports To field on Contact object is published from Force Corp Org)


Write a trigger on the Contact object to find the corresponding local record and assign it to the Reports To lookup field.

trigger ContactTrigger on Contact (after insert, after update) { 
  
    // define connection id for Force Corp Org. 
    Id networkId = ConnectionHelper.getConnectionId('ForceCorp'); 
    // build the ACME local contact id to Force Corp contact map 
    Map<Id,Id> contactIdMap = new Map<Id,Id>(); 
     
    for (Contact newContact : Trigger.new) { 
        if (newContact.remote_reports_to_id__c != null && newContact.remote_reports_to_id__c != '' && newContact.lastModifiedById = ConnectionHelper.getConnectionOwnerId('ForceCorp')) { 
            if (Trigger.isInsert 
                || (Trigger.isUpdate 
                    && (Trigger.oldMap.get(newContact.Id).remote_reports_to_id__c != newContact.remote_reports_to_id__c))) { 
               contactIdMap.put(newContact.id, newContact.remote_reports_to_id__c); 
            } 
        } 
    } 
    
    // call future method to link local Contact corresponding to Force Corp's reports to contact. 
    if (contactIdMap.size() > 0) { 
        ExternalSharingHelper.linkContact(contactIdMap); 
    } 
} 

The hard work of doing the linking is defined as a future method in an external class, ExternalSharingHelper:

public class ExternalSharingHelper { 
    
    @future 
    public static void linkContact(Map<Id,Id> contactIdMap) { 
  
        Set<Id> partnerContactIdSet = new Set<Id>(); 
        for (Id partnerContactId : contactIdMap.values()) { 
            partnerContactIdSet.add(partnerContactId); 
        } 
        
        Map<Id,Id> contactMap = new Map<Id,Id>(); 
        
        for (PartnerNetworkRecordConnection contactConnection : 
            [SELECT Id, Status, ConnectionId, PartnerRecordId, LocalRecordId 
             FROM PartnerNetworkRecordConnection 
             WHERE PartnerRecordId in :partnerContactIdSet]) { 
                
            if ( contactConnection.Status == 'Sent' || contactConnection.Status == 'Received') {                                                                
                contactMap.put(contactConnection.PartnerRecordId, contactConnection.LocalRecordId); 
            } 
        } 
  
        List<Contact> localContactList = new List<Contact>(); 
        
        for (Id contactId : contactIdMap.keySet()) { 
            Contact contactForUpdate = new Contact(id=contactId); 
            contactForUpdate.ReportsToId  =  contactMap.get(contactIdMap.get(contactId)); 
            
            localContactList.add(contactForUpdate);                                                          
        }                                                            
        
        database.update(localContactList); 
    }            


Best Practice: Forcing Updates to return generated case numbers to the source org

Challenge: You need the case number generated in the target org back in the source org. The case number in the target org (ACME) will not flow back to the custom field in the Force Corp org until the Case record is updated in the ACME org after initial record creation, as Salesforce to Salesforce will not flow data unless it sees an update to a record.

Solution: Do a pseudo update on the newly-created case in the ACME org via a future method. Here's the setup!

ACME Corp Org (Target Org) Setup

From ACME org, publish the Case Number field on the Case object (Go to the Force Corp connection, click Publish/Unpublish, select Case object, and Save. Click Edit against Case in the Publish/Unpublish section, select Case Number, and Save.)

Force Corp Org (Source Org) Setup

In the Force Corp Org, create a new field on Case object in the Force Corp org, let's call it Acme_Case_Number of Data Type Text (30)

Go to the Connection tab, click on the ACME connection, click Subscribe/Unsubscribe, and map the Case to Case object and Save. Click on the Edit link against Case in the Subscribe/Unsubscribe section.

Map the Case Number to the ACME Case Number field and click Save.

Now you need to define a trigger on the Case object. It will fire after a new case record is created, and if the conditions are correct, call our helper method to do the "touch" - a write to active the Salesforce to Salesforce functionality:

trigger CaseTrigger on Case (after insert) { 
    
    try { 
        ExternalSharingHelper sharingHelper = new ExternalSharingHelper(); 
        Id connectionId = ConnectionHelper.getConnectionId('ForceCorp');        
    
        // for cases coming from Force Corp org - need to push the case number 
        // do a fake touch 
        Set<Id> newCaseIds = new Set<Id>(); 
        
        for (Case newCase : Trigger.new) { 
            if (newCase.ConnectionReceivedId != null && newCase.ConnectionReceivedId == connectionId) { 
                newCaseIds.add(newCase.Id); 
            } 
        } 
        
        if (newCaseIds.size() > 0) { 
            ExternalSharingHelper.touchCases(newCaseIds); 
        } 
    } catch (System.Exception ex) { 
        // do nothing for now 
        System.debug(LoggingLevel.Error, 'case share trigger failed -' + ex.getMessage()); 
    } 
} 

Here's our helper class, which again uses a future method:

public class ExternalSharingHelper { 
    
@future 
    public static void touchCases(Set<Id> caseids) { 
        List<Case> caseList = [SELECT Id FROM Case Where id in :caseids]; 
        update caseList; 
    } 
} 

Note how this simply does an update on the list of cases. The update will fire the Salesforce to Salesforce.

Best Practice: Reflect Case comment status changes in the target org

Challenge: Delete Case comments from the target org if Case comments in the source org are made private or deleted.

Solution: In an Apex batch, check the PartnerNetworkRecordConnection, locate the record connection status, and delete the local record if the shared record from the source org is deleted. Schedule the batch to run every hour. The Salesforce.com UI does not permit scheduling to less than a weekly or monthly frequency. To schedule the following batch programatically, you can create a Visualforce page and call Apex code to do the scheduling (the code sample is below the batch code.)

First here's the code that will be batched:

global class ExternalSharingCleanupBatch implements Schedulable, Database.Batchable <sObject>{ 
  
    global void execute(SchedulableContext SC) { 
        ExternalSharingCleanupBatch cleanupBatch = new ExternalSharingCleanupBatch(); 
        ID batchprocessid = Database.executeBatch(cleanupBatch); 
    } 
    
    
    global Database.QueryLocator start(Database.BatchableContext BC) { 
        ExternalSharingHelper sharingHelper = new ExternalSharingHelper(); 
        Id networkId = sharingHelper.getConnectionId(); 
        return Database.getQueryLocator([SELECT localrecordid 
                                         FROM PartnerNetworkRecordConnection 
                                         WHERE (status = 'Deleted' OR status = 'Inactive') 
                                         AND connectionid = :networkId 
                                         AND enddate = LAST_N_DAYS:30]); 
    } 
  
    global void execute(Database.BatchableContext BC, List<sObject> batch) { 
         
        List<PartnerNetworkRecordConnection> recordConnectionBatch = (List<PartnerNetworkRecordConnection>)batch; 
        
        Set<Id> recordConnectionIds = new Set<Id>(); 
        for (PartnerNetworkRecordConnection recordConnection : recordConnectionBatch) { 
            recordConnectionIds.add(recordConnection.localrecordid);            
        } 
  
        /*******  delete - case comments which are either deleted or made private in ACME Org *******/ 
        List<CaseComment> caseCommentsToBeDeleted ; 
        
        caseCommentsToBeDeleted = [Select id from CaseComment where id in :recordConnectionIds]; 
        
        if (caseCommentsToBeDeleted.size() > 0) { 
            Database.delete(caseCommentsToBeDeleted,false); 
        } 
        
        /******* delete case attachments which are either deleted or made private in ACME Org *******/ 
        List<Attachment> attachmentsToBeDeleted ; 
  
        attachmentsToBeDeleted = [Select id from Attachment where id in :recordConnectionIds]; 
        
        if (attachmentsToBeDeleted.size() > 0) { 
            Database.delete(attachmentsToBeDeleted,false); 
        } 
  
        /******* delete cases which are either deleted or unshared from ACME Org *******/ 
        List<Case> casesToBeDeleted = [Select id from Case where id in :recordConnectionIds]; 
  
        if (casesToBeDeleted.size() > 0) { 
            Database.delete(casesToBeDeleted,false); 
        }        
  
        
        /******* delete contacts which are either deleted or unshared from ACME Org *******/ 
        List<Contact> contactsToBeDeleted = [Select id from Contact where id in :recordConnectionIds]; 
  
        if (contactsToBeDeleted.size() > 0) { 
            Database.delete(contactsToBeDeleted,false); 
        }         
        
        /******* delete accounts which are either deleted or unshared from ACME Org *******/ 
        List<Account> accountsToBeDeleted = [Select id from Account where id in :recordConnectionIds]; 
  
        if (accountsToBeDeleted.size() > 0) { 
            Database.delete(accountsToBeDeleted,false); 
        } 
    } 
    
    global void finish(Database.BatchableContext BC) { 
        
    } 
}      

To schedule this programmatically, put this code in a controller and call it from a Visualforce page.

try { 
    ExternalSharingCleanupBatch cleanupSchedulableBatch = new ExternalSharingCleanupBatch(); 
    String sch = '0 0 * * * ?'; 
    system.schedule('ExternalSharingCleanupBatch', sch, cleanupSchedulableBatch); 
    ApexPages.addMessage(new ApexPages.message(ApexPages.severity.INFO,'External Sharing Cleanup Job Scheduled')); 
            
} catch (System.Exception ex) { 
    ApexPages.addMessage(new ApexPages.message(ApexPages.severity.WARNING,ex.getMessage())); 
} 

Best Practice: Propagating Account and Contact merges

Challenge: The Account Merge and Contact merge in the source org (Force Corp Org) are not propagated to the target org (ACME Corp).

Note: Any child records that are reparented as a result of the merge operation do not fire triggers. See the docs for more information on this.

Solution:

Account Merge: As a result of an Account merge, reparenting is done on child records of losing accounts. (In a merge, there is a survivor account and a victim account. The victim account, or losing account as we refer to it here, gets deleted from the org.) The child records for the account are contacts and cases. Assuming that both accounts' child records are already shared externally (via Salesforce to Salesforce), we will need to propagate the new relationship between the losing account's children records and winner accounts in the target org. Contact Merge: As a result of contact merge, reparenting is done on child records of losing contact. The child records for contacts are cases. Assuming that both contacts' child records are already shared externally (via Salesforce to Salesforce), we will need to propagate the new relationship between the losing contacts' children records and winner contacts in the target org.

Force Corp Org (Source Org) Setup

Contact Object:

  1. Create a new formula field of type text, label it as S2S AccountId, API name: S2S_AccountId__c.

Formula text: For AccountId, select a profile corresponding to the user having "manage connection" permissions.

Case Object:

  1. Create a new formula field of type text, label it as S2S AccountId, API name: S2S_AccountId__c. Formula text: For AccountId, select a profile corresponding to the user having "manage connection" permissions.
  2. Create another new formula field of type text, label it as S2S Contact Id, API name: S2S_ContactId__c. Formula text: For ContactId, select a profile corresponding to the user having "manage connection" permissions.


ACME Corp Org (Target Org) Setup

Contact Object:

  1. Create a new field of type text, size 80, label it as S2S AccountId, API name: S2S_AccountId__c.

Case Object:

  1. Create a new field of type text, size 80, label it as S2S AccountId, API name: S2S_AccountId__c.
  2. Create another new field of type text, size 80, label it as S2S Contact Id, API name: S2S_ContactId__c.


Force Corp Org (Source Org) Setup:

First, here are the changes in the source org.

Account Merge:

The following Account trigger will update account references on all the contacts and cases under the losing account. To propagate the change of account ID on contact and case, do a pseudo update on Contacts and Cases in the source org.

trigger AccountMergeTrigger on Account (before delete) { 
    try { 
           if (Trigger.isBefore && Trigger.isDelete) { 
            
           // call @future method to do a pseudo update on the contacts so that the reparenting flows via S2S 
           // Cases under contacts would reparented automatically on source org side 
           // Case under contacts on target Org side would be updated as part of contact update trigger 
       List<Contact> contactListForUpdate = [SELECT Id from Contact Where Accountid in :Trigger.old]; 
       Map<Id,Contact> contactMap = new Map<Id,Contact>(); 
       contactMap.putAll(contactListForUpdate); 
            
           if (contactMap.keySet().size() > 0) { 
            ExternalSharingHelper.touchContactsForAccountMerge(contactMap.keySet());    
        } 
            
            // Case under contact would automatically be updated in Target org 
            // Only update cases which does not have contact but has an associated account which got merged with the new account. 
        List<Case> caseListForUpdate = [SELECT Id from Case Where Accountid in :Trigger.old and contactid = null]; 
        Map<Id,Case> caseMap = new Map<Id,Case>(); 
        caseMap.putAll(caseListForUpdate); 
            
            if (caseMap.keySet().size() > 0) { 
               ExternalSharingHelper.touchCases(caseMap.keySet());    
        } 
     }   
  
   } catch (System.Exception ex) { 
        // do nothing for now 
        System.debug(LoggingLevel.Error, 'Account Merge trigger failed -' + ex.getMessage()); 
    } 
}        

Here's the helper class:

public with sharing class ExternalSharingHelper { 
    
    @future 
    public static void touchCases(Set<Id> caseids) { 
        List<Case> caseList = [SELECT Id FROM Case Where id in :caseids]; 
        database.update(caseList,false); 
    } 
    
    @future 
    public static void touchContactsForAccountMerge(Set<Id> contactids) { 
        List<Contact> contactList = [SELECT Id FROM Contact Where id in :contactids]; 
        database.update(contactList,false); 
    }    
}      

Contact Merge: This trigger will update the Contact reference to winning contacts on all the cases under losing contacts. To propagate change on the case to the target org, do a pseudo update on cases under the losing contacts in the source org.

  
trigger ContactMergeTrigger on Contact (before delete) { 
    try { 
        if (Trigger.isBefore && Trigger.isDelete) { 
            // fire a fake update on the children of contact 
            
            // call @future method to do a fake update on the cases so that the reparenting flows via S2S 
            List<Case> caseListForUpdate = [SELECT Id from Case Where contactid in :Trigger.old]; 
            Map<Id,Case> caseMap = new Map<Id,Case>(); 
            caseMap.putAll(caseListForUpdate); 
            
            if (caseMap.keySet().size() > 0) { 
               ExternalSharingHelper.touchCases(caseMap.keySet());    
            } 
        
        } 
    } catch (System.Exception ex) { 
        // do nothing for now 
        System.debug(LoggingLevel.Error, 'ContactMergeTrigger failed -' + ex.getMessage()); 
    } 
}      

ACME Corp Org (Target Org) Setup:

Now let's look at the changed in the target org.

Account Merge: The Contact update will populate new Account references in the S2S_AccountId__c field on the Contact object. Use the account ID corresponding to the account in the source org to fetch S2S-linked account IDs in the target org, and update the account ID to the contact's account, as well as the Account reference on Cases under updated Contact.


trigger ContactExternalShareTrigger on Contact (before update) { 
    /* Check if the contact is relinked to another account, if the account ID on contact has changed then find the corresponding local account id and link to the contact as well as to cases under the contact */ 
        
    Map<Id,Id> contactAccountIdMap = new Map<Id,Id>(); 
        
    for (Contact updatedContact : TRIGGER.new) { 
            
        Contact oldContact = Trigger.oldMap.get(updatedContact.Id); 
        // Check if there is any change in Account Id on contact 
             
        if (updatedContact.S2S_AccountId__c != null || updatedContact.S2S_AccountId__c = '') { 
                
            if (oldContact.S2S_AccountId__c != null ) || (oldContact.S2S_AccountId__c != updatedContact.S2S_AccountId__c)) { 
                   contactAccountIdMap.put(updatedContact.id, updatedContact.S2S_AccountId__c); 
         } 
      } else if (oldContact.S2S_AccountId__c != null)) { 
                updatedContact.AccountId = null; 
                updatedContact.Account = null; 
      } 
  
   } 
  
    // Call @future method to update contacts and corresponding cases with new local account. 
    if (contactAccountIdMap.size() > 0) { 
      ExternalSharingHelper.linkContactsAndCases(contactAccountIdMap); 
   } 
} 

Contact Merge

The Case update will populate new Contact references in the S2S_ContactId__c field on the Case object. Use the contact ID corresponding to the contact in the source org to fetch S2S-linked contact IDs in the target org and update the contact ID to the case's contact.

trigger CaseExternalShareTrigger on Case (after insert, before update) { 
  
    /* Check if there is any change in either account ID or contact ID on the case, if yes then find the corresponding 
        local account ID and local contact ID and link to the case*/ 
           
    Map<Id,Id> caseContactIdMap = new Map<Id,Id>(); 
    Map<Id,Id> caseAccountIdMap = new Map<Id,Id>(); 
        
    for (Case updatedCase : TRIGGER.new) { 
        
    Case oldCase = Trigger.oldMap.get(updatedCase.Id); 
    // Check if there is any change in Account Id and Contact Id on Case 
    if (CommonUtil.isNotEmpty(updatedCase.AcmeOrg_ContactId__c)) { 
        
            if (CommonUtil.isEmpty(oldCase.AcmeOrg_ContactId__c) || (oldCase.AcmeOrg_ContactId__c != updatedCase.AcmeOrg_ContactId__c)) { 
               caseContactIdMap.put(updatedCase.id, updatedCase.AcmeOrg_ContactId__c); 
            } 
    } else if (CommonUtil.isNotEmpty(oldCase.AcmeOrg_ContactId__c)) { 
        updatedCase.ContactId = null; 
        updatedCase.Contact = null; 
    } 
    
    if (CommonUtil.isNotEmpty(updatedCase.AcmeOrg_AccountId__c)) { 
        
        if (CommonUtil.isEmpty(oldCase.AcmeOrg_AccountId__c) || (oldCase.AcmeOrg_AccountId__c != updatedCase.AcmeOrg_AccountId__c)) { 
           caseAccountIdMap.put(updatedCase.id, updatedCase.AcmeOrg_AccountId__c); 
        } 
    } else if (CommonUtil.isNotEmpty(oldCase.AcmeOrg_AccountId__c)) { 
        updatedCase.AccountId = null; 
        updatedCase.Account = null; 
    } 
  
    // Call @future method to update contact and account for the case 
    if (caseAccountIdMap.size() > 0 || caseContactIdMap.size() > 0) { 
       ExternalSharingHelper.linkCases(caseAccountIdMap,caseContactIdMap); 
    } 
}     

Here's a little utility class we use:

public with sharing class CommonUtil { 
  
    public static Boolean isEmpty(String str) { 
        return !isNotEmpty(str); 
    } 
    
    public static Boolean isNotEmpty(String str) { 
         return (str != null && str.trim().length() > 0); 
    } 
} 

And here's the external class that does the work:

public with sharing class ExternalSharingHelper { 
    public Id getConnectionId() { 
        
        // Select the account ids to be externally shared from custom settings 
        Env_Configuration__C EnvConfigS2S = Env_Configuration__C.getInstance('S2SConnectionIdACMEOrg'); 
        
        if (EnvConfigS2S == null) { 
             return null; 
        } 
        
        return EnvConfigS2S.Value__C;                            
    } 
    
    public Boolean isRecordConnectionActive(String status) { 
        if (status == null) return false; 
        
        if (status.equalsIgnoreCase('sent') || status.equalsIgnoreCase('received')) { 
            return true; 
        } 
        return false; 
    }    
  
    @future 
    public static void linkDesignatedContacts(Map<Id,Id> designatedContactIdPartnerContactIdMap) { 
        
        ExternalSharingHelper sharingHelper = new ExternalSharingHelper(); 
        Set<Id> partnerContactIdSet = new Set<Id>(); 
        for (Id partnerContactId : designatedContactIdPartnerContactIdMap.values()) { 
             partnerContactIdSet.add(partnerContactId); 
        } 
        
        Map<Id,Id> contactMap = new Map<Id,Id>(); 
        
        for (PartnerNetworkRecordConnection contactConnection : 
            [SELECT Id, Status, ConnectionId, PartnerRecordId, LocalRecordId 
             FROM PartnerNetworkRecordConnection 
             WHERE PartnerRecordId in :partnerContactIdSet]) { 
                
            if (sharingHelper.isRecordConnectionActive(contactConnection.Status)) {                                                                 
                contactMap.put(contactConnection.PartnerRecordId, contactConnection.LocalRecordId); 
            } 
        } 
  
        List<Designated_Contact__c> designatedContactList = new List<Designated_Contact__c>(); 
        
        for (Id designatedContactId : designatedContactIdPartnerContactIdMap.keySet()) { 
            Designated_Contact__c desginatedContactForUpdate = new Designated_Contact__c(id=designatedContactId); 
            desginatedContactForUpdate.Contact__c =  contactMap.get(designatedContactIdPartnerContactIdMap.get(designatedContactId)); 
            
            designatedContactList.add(desginatedContactForUpdate);                                                          
        }                                                            
        
        update designatedContactList; 
    } 
  
        
    @future 
    public static void linkCases(Map<Id,Id> caseAccountIdMap,Map<Id,Id> caseContactIdMap) { 
  
        ExternalSharingHelper sharingHelper = new ExternalSharingHelper(); 
        // Create a map of contact id and account id to corresponding local contact and local account id. 
        Map<Id,Id> recordConnectionMap = new Map<Id,Id>(); 
        
        List<Id> acmeOrgRecords = caseContactIdMap.values(); 
        acmeOrgRecords.addAll(caseAccountIdMap.values()); 
  
        for (PartnerNetworkRecordConnection contactConnection : 
            [SELECT Id, Status, ConnectionId, PartnerRecordId, LocalRecordId 
             FROM PartnerNetworkRecordConnection 
             WHERE PartnerRecordId in :acmeOrgRecords 
             AND ConnectionId = :sharingHelper.getConnectionId()]) { 
                
            if (sharingHelper.isRecordConnectionActive(contactConnection.Status)) {                                                                    
                recordConnectionMap.put(contactConnection.PartnerRecordId, contactConnection.LocalRecordId); 
            } 
        } 
  
        Map<Id,Case> caseMap = new Map<Id,Case>(); 
         
        for (Id caseIdForContactUpdate : caseContactIdMap.keySet()) { 
            Case caseForUpdate = new Case(id=caseIdForContactUpdate); 
            caseForUpdate.ContactId = recordConnectionMap.get(caseContactIdMap.get(caseIdForContactUpdate)); 
            caseMap.put(caseIdForContactUpdate, caseForUpdate); 
        } 
        
        for (Id caseIdForAccountUpdate : caseAccountIdMap.keySet()) { 
            Case caseForUpdate = caseMap.get(caseIdForAccountUpdate); 
            if ( caseForUpdate == null) { 
                caseForUpdate = new Case(id=caseIdForAccountUpdate); 
            } 
            
            caseForUpdate.AccountId = recordConnectionMap.get(caseAccountIdMap.get(caseIdForAccountUpdate)); 
            caseMap.put(caseIdForAccountUpdate, caseForUpdate); 
        } 
        
        database.update(caseMap.values(),false); 
    } 
  
    @future 
    public static void linkContactsAndCases(Map<Id,Id> contactAccountIdMap) { 
  
        ExternalSharingHelper sharingHelper = new ExternalSharingHelper(); 
        // Create a map of contact id and account id to corresponding local contact and local account id. 
        Map<Id,Id> recordConnectionMap = new Map<Id,Id>(); 
        
        List<Id> acmeOrgRecords = contactAccountIdMap.values(); 
  
        for (PartnerNetworkRecordConnection contactConnection : 
            [SELECT Id, Status, ConnectionId, PartnerRecordId, LocalRecordId 
             FROM PartnerNetworkRecordConnection 
             WHERE PartnerRecordId in :acmeOrgRecords 
             AND ConnectionId = :sharingHelper.getConnectionId()]) { 
                
            if (sharingHelper.isRecordConnectionActive(contactConnection.Status)) {                                                                   
                recordConnectionMap.put(contactConnection.PartnerRecordId, contactConnection.LocalRecordId); 
            } 
        } 
  
        List<Contact> contactList = new List<Contact>(); 
        
        for (Id contactIdForAccountUpdate : contactAccountIdMap.keySet()) { 
            Contact contactForUpdate = new Contact(id=contactIdForAccountUpdate); 
            contactForUpdate.AccountId = recordConnectionMap.get(contactAccountIdMap.get(contactIdForAccountUpdate)); 
            contactList.add(contactForUpdate); 
        } 
        
        database.update(contactList,false); 
        
        List<Case> casesToBeUpdated = new List<Case>(); 
        
        // get all the cases corresponding to updated contact and update the account id for those cases. 
        for (Case updateCase : [Select id , accountid, contact.accountid from case where contactid in :contactList]) { 
             updateCase.AccountId = updateCase.Contact.Accountid; 
            casesToBeUpdated.add(updateCase); 
            
            if (casesToBeUpdated.size() == 200) { 
                 database.update(casesToBeUpdated,false); 
                 casesToBeUpdated.clear(); 
            }     
        } 
        
        if (casesToBeUpdated.size() > 0) { 
                database.update(casesToBeUpdated,false); 
        }   
    } 
         
    static testMethod void testSharing() { 
         ExternalSharingHelper sharingHelper = new ExternalSharingHelper(); 
         String connectionId = sharingHelper.getConnectionId(); 
         
         
        // Get an already shared account from settings 
        Id activeSharedAccountId; 
        Env_Configuration__C EnvConfigS2STestAccount = Env_Configuration__C.getInstance('S2STestAccountId'); 
        if (EnvConfigS2STestAccount != null) { 
            activeSharedAccountId = EnvConfigS2STestAccount.Value__C; 
        } 
  
        // Get an already shared contact (from above account) from settings 
        Id activeSharedContactId; 
        Env_Configuration__C EnvConfigS2STestContact = Env_Configuration__C.getInstance('S2STestContactId'); 
        if (EnvConfigS2STestContact != null) { 
            activeSharedContactId = EnvConfigS2STestContact.Value__C;  
        } 
        
        if (connectionId == null || activeSharedAccountId == null || activeSharedContactId == null) { 
             
            return; 
        } 
        
        // cast string to ID , if invalid Id then casting would fail. 
        Id checkId = connectionId;  
        
        
        // For active shared account, insert a new contact to ContactExternalShareTrigger 
        // and verify if partner network connection is created. 
        Contact contact1 = new Contact(LastName='Bloggs', FirstName='Joe', AccountId = activeSharedAccountId); 
        insert contact1; 
        
        // verify if a partner network connection for the contact is created. 
        PartnerNetworkRecordConnection contactSharingRecord =  
                              [SELECT p.Status, p.StartDate, p.RelatedRecords, p.PartnerRecordId, p.ParentRecordId 
                                      , p.LocalRecordId, p.Id, p.EndDate, p.ConnectionId 
                               FROM PartnerNetworkRecordConnection p              
                               WHERE p.LocalRecordId = :contact1.Id]; 
            
        System.assertEquals(contactSharingRecord.LocalRecordId, contact1.Id); 
        System.assertEquals(contactSharingRecord.ParentRecordId, activeSharedAccountId); 
         
        // For active account's contact , insert case and verify 
        
        Case case1 = new Case(Subject='test case 1', AccountId = activeSharedAccountId, contactId=activeSharedContactId); 
        insert case1; 
        
        PartnerNetworkRecordConnection caseSharingRecord =  
                      [SELECT p.Status, p.StartDate, p.RelatedRecords, p.PartnerRecordId, p.ParentRecordId 
                              , p.LocalRecordId, p.Id, p.EndDate, p.ConnectionId 
                       FROM PartnerNetworkRecordConnection p              
                       WHERE p.LocalRecordId = :case1.Id]; 
        
        System.assertEquals(caseSharingRecord.LocalRecordId, case1.Id); 
        System.assertEquals(caseSharingRecord.ParentRecordId, activeSharedContactId);         
         
    } 
}   

Summary

This article shows how two different Salesforce.com customers, or customers with two Force.com orgs, can automate data sharing between their Force.com orgs, using Salesforce to Salesforce native data sharing technology and Apex programming based on the Force.com platform. The programmatic approach not only automates data exchange, but also enables exchanged records to carry the lookup and parent-child relationship among objects from source org to parent org, and vice versa.

References