+ Start a Discussion
akallioWileyakallioWiley 

Learning Describe Methods

I am just starting to learn about Describe methods because I think it is what I need in order to do what I need to do. I am posting here to see if there are any more skilled developers that can tell me if I'm on the right path. So, here's the deal...

 

Our Contacts are synced with an ERP via Informatica. The ERP is capable of merging Contact records, but Informatica does not seem to be capable of passing those changes down to SFDC. So, I need to make those changes with a trigger.

I have it worked out so when a Merge takes place in the ERP, Informatica will pass the SFID of the master Contact record to the Child contact record. Which I can use to trigger a yet to be written merge class in apex.

 

My first hope was that I could simply use the merge dml statement, but learned that the statement can only do 3 records at atime. In other words, you can't 'bulkify' a merge dml.

 

So, now I am faced with recreating the merge statement. It seems I could do this by simply querying for all of the existing child objects of a Contact record. The problem there is that I will have to remember to add code whenever I add a new object as a child to the Contact object.

 

So, it seemed like a good opportunity to learn about the Describe methods.

 

My question is...Is this possible? Would it be possible to get a list of Contacts from a trigger, and use the Describe methods to dynamically query for all of the child objects of each Contact, and then actually make updates to the records in those child objects?

 

Thanks,

Andy

 

BrianWKBrianWK

Have you looked into batch Apex?

 

http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_batch_interface.htm

 

You could bulkify the Merge DML by using Batch Apex.

 

In it's very basic term a batch class contains a Query (all your contacts that you need to be merged for example) and then an execute method (The merge function). You can specify how many records to pass at one time to pass into the execute portion fo the class. For example, if your query returns 15 contacts you could send 3 at a time to the execute method and the code will repeat 5 times.

 

This way you don't have to worry about describes.

 

There are some restrictions to this method, you can only have 5 batches running at a given time. So you may need to do a double hitter on this. For example when informatica sends over your SFID have it placed in a hidden field on the contact. Then you could run the batch at will... or you could schedule it to run once a day (or something like that) based on the SFIDs in those contact fields. This would be safer than calling the Batch from a trigger since it's possible you could have more than 5 triggers attempting to call the batch.

 

 

BrianWKBrianWK

Let me give you an example of what I've done with Batch. Maybe it'll help.

 

Here's the scenario in my example:

 

I have custom objects called Territory__c and State__c. State__c has a lookup to Territory__c. Territory__c has a lookup to User. Accounts have multiple lookup fields to Territory__c and User.

 

We have mutliple Sales people per account (multiple types of sales people). An account can belong to multiple territories. The goal is to have Accounts updated based on the User referenced on Territories. So you set up your Territories, the States in that territory and the User that owns the territory.

 

Then you click a button that calls my Batch. The Batch queries all Accounts and in each "batch" sends a ist of accounts into a Class that I have do all the work. For those Accounts it looks at the State, identifies the Territories it belongs to and assigns the Territory and User (sales person) to that account.

 

Whew... okay long winded... Here's a snippet:

 

The batch Code:

 

global class Batch_Account_Territory_Management implements Database.Batchable<SObject>{
	//Query String Variable. This uses dynamic apex to get all the fields on the Account	
    public string query = 'select ' + Dynamic_Apex_Support.GetAllFields(Account.SObjectType) +	' from Account';
    //String for unit tests
	public string TestString = 'TestOfBatch';  	
	
	//This is the query method. This returns my list of Accounts based on the SoQL Query string defined above
    global database.querylocator start(Database.BatchableContext bc)
    {        
        return Database.getQueryLocator(query);
    }
    
	//This is the execute method. This is what happens for each "Batch"
    global void execute(Database.BatchableContext bc, sObject[] objects)
    {
		list<Account> lAccts = new list<Account>(); 		
 		for(sOBject s: objects) 
 		{
 			Account a = (Account)s;
 			lAccts.add(a);
 		}
 		system.debug('Batch_Account_Territory_Management_Class Sending Accounts to Class: ' + lAccts.size());		
 		system.debug('Standard Account Batch');
		//These are classes that actually do the "Work"
		Territory_Update_Class.TerritoryParser(lAccts);
 		Territory_Update_Class.UpdateAccountUsers(lAccts);
			  
    }
    //This is the finish method
    global void finish(Database.BatchableContext bc)
    {
       system.debug('all done');
    }
}

 You can call the batch to start from a different. In my case I created an "Admin Tools" visualforce page. This let's me see the number of active batches. I use a button on my VF page to call my batch classes

 

This isn't the full controll but a snippet:

public  class Administrator_Tools_Controller 
{
    //This is a list of Batch Jobs
	public List<BatchJob> batchJobs;
	
	//The variable for the number of batch Jobs
    public Integer numberOfJobs {get; set;}
	
	//Variable for any errors
	public string error {get;set;}
 
	//This is the method that gets the number of batch jobs
    public List<BatchJob> getBatchJobs()
    {
        //Create new list of BatchJobs, a wrapper class that includes the job and percent complete.
        batchJobs = new List<BatchJob>();
 
        //If number of jobs was not defined, default to 20
        if(numberOfJobs== null || numberofJobs <= 0){
            numberofJobs = 20;
        }
 
        //Query the Batch apex jobs
        for(AsyncApexJob a : [select TotalJobItems, Status, NumberOfErrors, MethodName, JobType, JobItemsProcessed, Id, CreatedDate, CreatedById, CompletedDate, ApexClassId, ApexClass.Name From AsyncApexJob where JobType = 'batchApex' order by CreatedDate desc limit :numberOfJobs]){
            Double itemsProcessed = a.JobItemsProcessed;
            Double totalItems = a.TotalJobItems;
 
            BatchJob j = new BatchJob();
            j.job = a;
 
            //Determine the pecent complete based on the number of batches complete
            if(totalItems == 0){
                //A little check here as we don't want to divide by 0.
                j.percentComplete = 0;
            }else{
                j.percentComplete = ((itemsProcessed  / totalItems) * 100.0).intValue();
            }
 
            batchJobs.add(j);
        }
        return batchJobs;
    }
       
		//This is the method I call from my button to start the batch
        public void StartBatchTerritory()
        {
                error = '';
                //create the "Job" which creates an instant of the batch class
				Batch_Account_Territory_Management job = new Batch_Account_Territory_Management();
                
				//Grabs existing jobs that are still open
                List<AsyncApexJob> openJobs = [select Id from AsyncApexJob where Status = 'Processing' OR Status = 'Queued']; 
				
				//logic that makes sure I don't have too many jobs open and stops me if I do
                if(openJobs.size() < 5)
                {
					//This actually starts the batch
					ID batchprocessid = Database.executeBatch(job);
                }else
                {
                        error = 'WHOA BUDDY! Only five batch jobs at a time.';
                }
        //EOM           
        }

    //This is the wrapper class the includes the job itself and a value for the percent complete
    public Class BatchJob{
        public AsyncApexJob job {get; set;}
        public Integer percentComplete {get; set;}
    }
 
    /*--------------------TEST METHOD------------------------*/
    static testMethod void batchStatusBarTest(){
        Administrator_Tools_Controller controller = new Administrator_Tools_Controller();
        controller.getBatchJobs();
    }
//eof
}

In this instance I'm just having the batch decide how many records to pass into the executive method. I could also call the batch class and specify how many at a time I want:

 

ID batchprocessid = Database.executeBatch(new atch_Account_Territory_Management(), 5);

 

 

 

 

And finally the page:

 

<apex:page controller="Administrator_Tools_Controller" id="ThePage">
 <apex:form >
	<!-- Here's the button that starts the batch -->
    <apex:commandButton value="Start Account Territory Batch Job" action="{!StartBatchTerritory}" reRender="jobs,error"/>    
    <!-- This displays the error -->
	<apex:outputText id="error" value="{!error}" style="font-weight: bold; color: red"/>
    <!-- This is a nifty component I uhm "borrowed" that displays the open batches -->
	<c:batchJobs id="jobs" numberOfJobs="20"/>
    </apex:form>
</apex:page>