UPDATE 25/04/2012: If cloning child objects using a custom button is of interest to you, check out my post regarding Clone Plus!!!
There are several scenarios during development where it becomes necessary to create complete copies of instances of Salesforce objects. Salesforce already provides a generic "clone" method to copy objects by casting them first to a more primitive base sObject. The clone method for sObjects even has a parameter which re-creates all relationships opt_IsDeepClone. The documentation describes that turning this option to true will result in a full copy of the sObject being returned by the method.
However, a common problem developers have when using this method is that it does not automatically copy all of the objects field values from the database. Instead, the opt_IsDeepClone option only copies the field values currently held in memory that have been retrieved through use of a SOQL query or have been populated directly in the Apex code.
public class SObjectAllFieldCloner { // Clone a list of objects to a particular object type // Parameters // - List<sObject> sObjects - the list of objects to be cloned // - Schema.SobjectType objectType - the type of object to be cloned. // The sObjects you pass in must include the ID field, // and the object must exist already in the database, // otherwise the method will not work. public static List<sObject> cloneObjects(List<sObject> sObjects, Schema.SObjectType objectType){ // A list of IDs representing the objects to clone List<Id> sObjectIds = new List<Id>{}; // A list of fields for the sObject being cloned List<String> sObjectFields = new List<String>{}; // A list of new cloned sObjects List<sObject> clonedSObjects = new List<sObject>{}; // Get all the fields from the selected object type using // the get describe method on the object type. if(objectType != null){ sObjectFields.addAll( objectType.getDescribe().fields.getMap().keySet()); } // If there are no objects sent into the method, // then return an empty list if (sObjects != null && !sObjects.isEmpty() && !sObjectFields.isEmpty()){ // Strip down the objects to just a list of Ids. for (sObject objectInstance: sObjects){ sObjectIds.add(objectInstance.Id); } /* Using the list of sObject IDs and the object type, we can construct a string based SOQL query to retrieve the field values of all the objects.*/ String allSObjectFieldsQuery = 'SELECT ' + sObjectFields.get(0); for (Integer i=1 ; i < sObjectFields.size() ; i++){ allSObjectFieldsQuery += ', ' + sObjectFields.get(i); } allSObjectFieldsQuery += ' FROM ' + objectType.getDescribe().getName() + ' WHERE ID IN (\'' + sObjectIds.get(0) + '\''; for (Integer i=1 ; i < sObjectIds.size() ; i++){ allSObjectFieldsQuery += ', \'' + sObjectIds.get(i) + '\''; } allSObjectFieldsQuery += ')'; try{ // Execute the query. For every result returned, // use the clone method on the generic sObject // and add to the collection of cloned objects for (SObject sObjectFromDatabase: Database.query(allSObjectFieldsQuery)){ clonedSObjects.add(sObjectFromDatabase.clone(false,true)); } } catch (exception e){ // Write exception capture method // relevant to your organisation. // Debug message, Apex page message or // generated email are all recommended options. } } // return the cloned sObject collection. return clonedSObjects; } }
An example of how you would call this method to clone an Account object from another Apex controller is:
Account originalAccount = [select Id from Account where name = 'My Favourite Account']; sObject originalSObject = (sObject) originalAccount; List<sObject> originalSObjects = new List<sObject>{originalSObject}; List<sObject> clonedSObjects = SObjectAllFieldCloner.cloneObjects( originalSobjects, originalSobject.getsObjectType()); Account clonedAccount = (Account)clonedSObjects.get(0);
Notes: This query only copies a single type of object at a time, related child objects are not cloned, however all lookup relationships/master record references are copied. Also in this example, I have shown how to create a virtual "SELECT * FROM" to make this resistant to change. This type of query should only be used when absolutely necessary, never out of developer laziness.