Today while building the page for S2S connection and record management, I have faced with issue of the necessity to filter object types that are available for sharing. It's inconvenient that PartnerNetworkConnection object does not have information what exact objects are published/subscribed in connection. While investigating this issues, I found out that the objects that can be shared have two similar fields: ConnectionSentId and ConnectionReceivedId. So to form a list of object types that are available for sharing we need to go through all objects and check up if their fieldset contains these fields. As in the example:
But in our case we have more than 100 objects that are available for sharing and this code gets an error:
System.LimitException: Too many fields describes: 101
Thus we can create one example of the object and try to add these fields. If the object can be shared we will have the error that this field is not writeable:
System.SObjectException: Field ConnectionSentId is not editable
In case this object is not shareable the error will be:
System.SObjectException: Invalid field ConnectionSentId for Training_Class_Attendee__History
Standard try/catch functionality in Salesforce include methods that allow you to add errors to appropriate objects in lists that was used in DML operation. Why it is so important to know what object failed and why? To answer this question lets look what happens if list of objects are inserted, for example ten Projects. If one Projects fails due to some error, validation rule, required field is not filled in or something else, all list will not be inserted. It means that valid Projects too. Another example, group of Opportunities are inserted, and trigger fires on after insert creates Project for each one. And the same thing - we have different errors on insert of several Projects. What to do with this errors? We need to show each one on appropriate Opportunity. I this cases try/catch has methods that allow us to find this failed objects and to track which object failed due to which error. Let's consider the following two cases:
Insert of Project from Opportunity trigger.
Update of Project from Opportunity trigger.
They are different because in first one Project fails on insert, and it means it do not have Id. So to identify what what Project or Projects fail we will use DMLException methods such as getNumDml and getDmlIndex. getNumDml() will return us number of failed objects and getDmlIndex() - row number of failed one. Lets look into simple trigger:
trigger OpportunityUpdateProjectTrigger on Opportunity ( after insert ) {
// Create List of Projects that we will insert.
List<Project__c> createProjectList = new List<Project__c>();
// Going through all Opportunities and creating Project for each one.
for( Opportunity opportunityItem : trigger.new )
createProjectList.add( new Project__c( Name = opportunityItem.Name,
Opportunity__c = opportunityItem.Id ) );
if ( !createProjectList.isEmpty() ){
try{
// Trying to insert Projects.
insert createProjectList;
} catch( DMLException dmlEx ){
// Going through all errors.
for( Integer i = 0; i < dmlEx.getNumDml(); i++ ){
String errorMessage = 'Error on creation of Project: ' + dmlEx.getDmlMessage( i );
// Attaching error to appropriate Opportunity from Trigger.newMap.
trigger.newMap.get( createProjectList[ dmlEx.getDmlIndex( i ) ].Opportunity__c ).addError(errorMessage);
}
}
}
}
Lets look what we can use in second case, where we have try/catch on update. Here we have update of Project on update of Opportunity. Trigger on Opportunity after update will query from database Projects that are related to this Opportunities, make some changes and update them. On update of Projects can be errors that we need to display on appropriate Opportunity, like in first case. But here we have Project Id. For this we will use DMLException method getDmlId, that returns Id of failed objects. We can use getDmlIndex, like previously. But in this case it would be better to use DMLException method getDmlId() will return Id of failed object. Lets look in to the code:
trigger OpportunityUpdateProjectTrigger on Opportunity ( after update ) {
// Create Map of Projects that we will updated.
Map<id,Project__c> projectsToUpdateMap =
new Map<id,Project__c>( [SELECT Id, Opportunity__c
FROM Project__c
WHERE Opportunity__c IN : trigger.newMap.keySet() ] );
/*
Here we have some updates on projects.
*/
if( !projectsToUpdateMap.keySet().isEmpty() ){
try{
// Trying to update Projects.
update projectsToUpdateMap.values();
} catch( DMLException dmlEx ){
for( Integer i = 0; i < dmlEx.getNumDml(); i++ ){
String errorMessage = 'Error creation of Project: ' + dmlEx.getDmlMessage( i );
// Attaching error to appropriate Opportunity from Trigger.newMap.
trigger.newMap.get( projectsToUpdateMap.get( dmlEx.getDmlId( i ) ).Opportunity__c ).addError( errorMessage );
}
}
}
}
So now we know we know how to add errors to failed one, and can make one more thing. In both cases valid Projects were not inserted, and as they didn't get errors we can try insert them in catch. For this we need to create new list of Projects that will include only valid ones and insert it.
...
try{
insert createProjectList;
} catch( DMLException dmlEx ){
// Crete new list of Projects to insert and fill it from old one
List<Project__c> validProjectsToInsert = createProjectList;
// Going through all errors.
for( Integer i = 0; i < dmlEx.getNumDml(); i++ ){
String errorMessage = 'Error on creation of Project: ' + dmlEx.getDmlMessage( i );
trigger.newMap.get ( createProjectList[ dmlEx.getDmlIndex( i ) ].Opportunity__c ).addError(errorMessage);
// Remove failed one
validProjectsToInsert.remove( dmlEx.getDmlIndex( i ) );
}
// And final insert of valid
insert validProjectsToInsert;
}
...
In the end want to say that we can also use getDmlType() and getDmlStatusCode() to identify what specific error was. Or getDmlFields() with getDmlFieldNames() to know if error was on fields.