Logging Deleted Records in Salesforce.com
mgsmith | Tuesday, March 10th, 2009 | 2 Comments »Note: The code for all triggers, classes, and pages can be downloaded as an archived Eclipse IDE Project from here, or you can install this as a package into your Development or Sandbox environment from here.
The Business Issues:
- As an administrator, there are times when I wanted to monitor records deleted, especially for certain users who had a habit of inadvertently or even purposely deleting records.
- The Recycle bin does not list Opportunities and Contacts deleted when their parent account is deleted. There were times when I was contacted to search for the “missing” Contact or Opportunity, but it was nearly impossible to locate because the Account associated with the record was deleted and the Contact/Opportunity did not show up in the Recycle bin.
The Solution:
- Create Apex Triggers on key objects in SalesForce.com to log to a custom object when records were Deleted and Undeleted. To log deletes on your custom objects, simply use the IDE to create new Trigger on your custom object and then copy the trigger code from one of the existing standard object triggers. You’ll also need to modify the DeleteLog_Test class to add test coverage for your new trigger. The test code can likely just be copied for an existing standard object and then modified with your object name.
- Build logic into the code to also log deleted child records, especially for Contacts and Opportunities. It is fairly straight forward to extend this logic to log other child records such as Contracts or Assets by modifying the query in the commitInsert() method in the DeleteLog class.
Technical Issues:
- Avoid hitting the maximum number of SOQL and Script Execution Statement limits in bulk deletes, such as when using the “Mass Delete Accounts” function. After some experimenting, the key to avoiding this issue involved re-writing the processing of Notes/Attachments/Tasks for records being deleted so that it first read in all of the items in a single query and then processed them in a more efficient loop using Map[]‘s instead of Lists to avoid using a For loop to process the QueryResults in List[] form.
- Creating a Test class for the Visual Force page. I kept getting an error about no constructor[] for the DeletedLog_Extension class. From what I read on the developer boards there is an issue with testing controller extensions. Luckily, there was a solution to the problem posted by mtbclimber. http://community.salesforce.com/sforce/board/message?board.id=Visualforce&message.id=695#M695.
- Optionally avoid logging of huge mass deletes by the administrator or by integrated applications. In my past role as an Administrator there were occasions where I would mass-delete a bunch of records, almost always to correct an import or Outlook Add-in sync problem with a user. We also had a couple of integrated applications that would upload and sometimes mass-delete hundreds of records on a daily basis. To avoid logging all of these in the DeleteLog table, there is a custom field added to the User object to allow the logging functionality to be disabled for a specific userID. If you install the package or use the archived IDE project for this, be sure to manually add the “Do Not Log Deleted Records” field to the User Page Layout.
The base Trigger logic is fairly simple.
trigger Account_LogDelete on Account (before delete, after undelete) {
DeleteLog LogDelete = New DeleteLog();
if (Trigger.isDelete) {
for (Integer i = 0; i < Trigger.old.size(); i++) {
LogDelete.LogDeletedRecord(Trigger.old[i].Name,
Trigger.old[i].Id,Trigger.old[i].getSObjectType().
getDescribe().getLabel(), '' );
}
LogDelete.CommitInsert();
}
if (Trigger.isUnDelete) {
for (Integer i = 0; i < Trigger.new.size(); i++) {
LogDelete.LogUnDeletedRecord(Trigger.new[i].Id);
}
LogDelete.CommitRecoveredUpdates();
}
}
It starts by creating a new instance of the DeleteLog class and then passes in the records from the Trigger.old[] collection to the LogDeletedRecord() or LogUnDeletedRecord() method depending on the type of action taken by the user. Finally, it calls the appropriate Commit() method to write the new records to the custom DeleteLog__c object.
The DeleteLog class has four main methods:
- logDeletedRecord()
- logUnDeletedRecord()
- commitInsert()
- commitRecoveredUpdates()
The logDeletedRecord() method essentially adds a new item to a class list of type DeleteLog__c. It also adds the ID of the object being deleted to an oID list. This will be used later to query all child records for the records being deleted. One key part of the method is that it forces a commit() at 150 records. The maximum number of records that can be inserted at one time is 200.
public void logDeletedRecord(string oName, string oID, string oType, string oParentID) {
DeleteLog__c deletedRecord = new DeleteLog__c(Name = oName,
ObjectID__c = oID, ObjectType__c = oType);
if (oParentID <> '') deletedRecord.ParentID__c = oParentID;
newRecords.Add(deletedRecord);
IDs.add(oID);
if (newRecords.size() > 149) {
CommitInsert();
newRecords.clear();
IDs.clear();
}
}
The commitInsert() method is where the SOQL logic had to be more creative to avoid hitting the governor limits for bulk operations. commitInsert() has three main steps:
- Count the number of Activities, Notes, and Attachments associated with each record being deleted.
- Write the DeleteLog records to the DeleteLog__c object
- If the object(s) being deleted are Accounts, find Contacts and Opportunities associated with the Accounts and add them to the DeleteLog__c object as well.
Finally, I added VisualForce page associated with viewing the DeleteLog__c object with an [Undelete] button. Though it is true that the button could have also been done using a JavaScript button on the standard page layout. What is nice about looking at a Deleted record, such as a Deleted Account, from this page is that we can see the Account along with the deleted child records listed.


Hi mgsmith,
you made it very cool and comfortable.
Nice Idea and very comfortable to pack it into project data for eclipse.
I’ve tried to add more fields for reference to relate them to the DeleteLog__c object. (for example, i want to show Lastname und Firstname form contact in the DeleteLog__c record. But an error messsage occur that told me that their is notation error (string, id, string, string). How can i ad more fields from deleted object record?
Thx and best regards,
Adrian
Adrian,
I assume you’re trying to pass the field values from the trigger to the class, which is why the notation error. The class itself would have to be modified to support accepting additional field values as parameters, though technically the class is written to be generic and work for pretty much any object. The class is already capturing the ‘name’ value as the first parameter, so at least for Contact that technically covers First & Last Name (assuming you don’t need to have them as separate fields).
If you need to have them separated in the DeleteLog, the class will need modified to support accepting additional values in the logDeletedRecord() call, and of course you’d have to make sure this doesn’t impact any other objects that are being logged.
Best Regards,
Mike