Affichage des articles dont le libellé est APEX. Afficher tous les articles
Affichage des articles dont le libellé est APEX. Afficher tous les articles

mardi 3 avril 2012

Envoyer des emails en APEX

La classe Messaging fournie par APEX permet d'envoyer des emails facilement, qu'ils contiennent du texte brut, du contenu HTML ou qu'ils reposent sur des modèles de message.
Nous allons, dans cet article, montrer deux méthodes implémentant les fonctionnalités de la classe Messaging.

Envoi d'un email en texte brut ou HTML

La méthode ci-dessous permet d'envoyer un email en spécifiant l'objet du message, son contenu et les destinataires.

public static void sendEmailWithText(String the_Object, String the_Text, Boolean the_IsHtml, String[] the_Recipients) {
                       
   // Check the limit of sent emails is not passed
   if (Limits.getEmailInvocations() >= Limits.getLimitEmailInvocations()) {
      return;
   }
                       
   Messaging.SingleEmailMessage a_Mail = new Messaging.SingleEmailMessage();
           
   if (the_Recipients != null) {
      // Set the destinary adresses
      a_Mail.setToAddresses(the_Recipients);
   }
   else {
      // Find the email of the current user
      User a_User = [SELECT Email 
                     FROM User 
                     WHERE Id=:Userinfo.getUserId()];
        
      // Set the destinary adresses
      a_Mail.setToAddressesnew String[] {a_User.Email );
                        
      if ((the_Object == null) || (the_Object == '')) {
         a_Mail.setSubject('NaTwelve : Custom email sent');
      }
      else {
         a_Mail.setSubject(the_Object);
      }
                       
      // Set the destinary adresses
      if (the_IsHtml) {
         a_Mail.setHtmlBody(the_Text);
      }
      else {
         a_Mail.setPlainTextBody(the_Text);
      }
       
      // Do not create an email task for this email
      a_Mail.setSaveAsActivity(false);
           
      // Send the email
      Messaging.sendEmail(new Messaging.SingleEmailMessage[] { a_Mail });
   }
}


Comment utiliser notre méthode ?

Avec un texte brut
sendEmailWithText('Objet de mon email', 'Corps de mon email\nEmail au format texte brut.', false, System.Userinfo.getUserId());


Avec de l'HTML
sendEmailWithText('Objet de mon email', '<h1>Corps de mon email</h1><br/>Email au format HTML.', true, System.Userinfo.getUserId());

Cette méthode peut être utilisée aussi bien pour envoyer des alertes, que des notifications ou des traces.
J'utilise, moi-même, cette fonction dans de nombreux services de messagerie afin de recevoir des traces de debug au format texte par email.

Envoi d'un email avec modèle de message

public static void sendEmailWithTemplate(String the_Template, Id the_RecipientId, Id the_ObjectId) {

                       
   // Check the limit of sent emails is not passed
   if (Limits.getEmailInvocations() >= Limits.getLimitEmailInvocations()) {
      return;
   }
                    
   // Get the email template
   EmailTemplate a_Template = [SELECT Id 
                               FROM EmailTemplate 
                               WHERE DeveloperName =:the_Template];
    
   Messaging.SingleEmailMessage a_Mail = new Messaging.SingleEmailMessage();
                       
   if (the_RecipientId != null) {
      // Set the destinary adresses
      a_Mail.setTargetObjectId(the_RecipientId);
   }
   else {
      // Set the destinary adresses of the current user
      a_Mail.setTargetObjectId(Userinfo.getUserId());
   }
                                  
   // Do not create an email task for this email
   a_Mail.setSaveAsActivity(false);
                                  
   // Set template of email
   a_Mail.setTemplateId(a_Template.Id);
                                  
   if (the_ObjectId != null) {
      // Set the resource demand object for merging fields
      a_Mail.setWhatId(the_ObjectId);
   }
                                  
   // Send the email
   Messaging.sendEmail(new Messaging.SingleEmailMessage[] { a_Mail });
}

Comment utiliser notre méthode ?

Account aAccount = [SELECT Id FROM Account LIMIT 1];
sendEmailWithTemplate('modeleFicheClient', System.Userinfo.getUserId(), aAccount.Id);

En utilisant des modèles de message, on gagne en généricité et on facilite la gestion du contenu des messages qui peut être fait par l'interface du CRM sans avoir à manipuler du code APEX.


Remarques

L'envoi d'email et les limites

Comme on peut le voir en début de nos deux méthodes, on vérifie que la limite EmailInvocations n'est pas dépassée. Force.com limite le nombre d'emails envoyer via la fonction Messaging.sendEmail à 10. Si vous ne respectez pas cette limite, vous verrez ce beau message d'erreur « Too many Email Invocations ».

D'où l'intérêt de grouper les destinataires pour l'envoi d'un même email ou l'utilisation des emails en masse pour l'envoi de contenus différents.

Les emails en masse

Nous avons utilisé la classe SingleEmailMessage dans nos deux méthodes mais il existe aussi la classe MassEmailMessage qui est plus approprié pour l'envoi de messages différents à plusieurs destinataires.
Elle sera particulièrement utile pour les emails qui utilisent des modèles de message. Le modèle sera alors appliqué à chacun des utilisateurs passés à la méthode setTargetObjectIds.

Sachez que l'envoi d'emails est soumis à deux autres limites mais plus rares cette fois-ci. La première empêche l'envoi à plus de 250, 500 ou 1000 (en fonction de l'édition de Salesforce que vous possèdez) destinataires différents et externes pour un même email en masse. La seconde fixe un maximum de 1000 emails envoyés en utilisant la classe SingleEmailMessage ou la classe MassEmailMessage par jour.


source : Outbound Email, Understanding Execution Governors and Limits

mardi 27 mars 2012

La puissance des requêtes SOQL

Le langage SOQL ou Salesforce Object Querying Language permet d'interroger la base de données de Force.com via des requêtes qui ne sont pas sans rappeler les requêtes SQL.

Le petit frère de SQL ?

Nous allons noter quelques différences majeures entre les langages SOQL et SQL :

Tout d'abord, il est impossible de sélectionner tous les champs d'une table en utilisant l'opérateur " * ". Vous ne verrez donc jamais cette requête :

[SELECT * FROM Account];

Ensuite, le langage SOQL ne peut être utilisé que pour extraire des données de la base de données, soit avec le mot-clé "SELECT". Pour tout ce qui est CRUD, il faudra alors se tourner vers les instructions DML que nous aborderons dans un prochain article.

Adieu CONCAT, UPPER, DATEDIF ou autre opérateur "+"... Hélas, SOQL est très pauvre en fonction et opérateurs. Et ne comptez pas utiliser des alias non plus.
On peut toutefois bénéficier des opérateurs suivants :
  • =, <, , > et pour comparer nombres et dates
  • =, LIKE (et son meilleur ami "%") pour comparer des chaînes de caractère
Il est important de noter qu'il est impossible de comparer deux champs entre eux dans une requête. La comparaison doit obligatoirement se faire entre un champ et une constante. Vous ne verrez donc jamais une requête de ce genre :

[SELECT Subject 
 FROM Event 
 WHERE EndDatetime > StartDatetime + 10];

Attendez ! Je vous vois venir... Vous allez me dire que cet article s'intitule "La puissance des requêtes SOQL" alors que jusqu'à maintenant ce que nous avons vu fait plus penser à de l'impuissance. Mais SOQL c'est aussi la capacité à extraire des données des tables parents et enfants en toute simplicité.

Un langage [très] relationnel

Les champs de types Lookup ou Master-detail permettent d'avoir des relations 1-n entre les tables de la base de données. Un exemple est le lien entre les tables Compte et Opportunité.
Comme c'est la référence du compte qui est stockée dans un champ de l'opportunité, nous disons que la table Compte est une table parent de la table Opportunité et, inversement, la table Opportunité est une table enfant de la table Compte.
En SQL, nous parlons de jointure droite ou jointure gauche.

Relation avec une table parent

Jointure externe droite
Ce type de jointure permet d'extraire des données d'une table parent.
Exemple : 
[SELECT Name, Position__r.Department__c FROM Job_Application__c];

Jointure interne droite
Cette jointure rajoute un filtre supplémentaire sur le résultat de la requête. Il faut que chaque enregistrement soit rattaché à un enregistrement parent.

Exemple :
[SELECT Name
 FROM Job_Application__c
 WHERE Position__r.Department__c=‘Sales’];

Anti-jointure droite
L'anti-jointure permet de ne pas extraire les éléments pour lesquels un enregistrement parent existe.

Exemple :
[SELECT Name FROM Job_Application__c WHERE Position__c = null];

Attention : Salesforce impose une limite de 25 relations vers des champs de tables parent.

Relation avec une table enfant

Jointure externe gauche
Ce type de jointure permet d'extraire des données d'une table enfant.

Exemple :
[SELECT Name, (SELECT Name FROM Job_Applications__r)
 FROM Position__c];

Jointure interne gauche
Cette jointure rajoute un filtre supplémentaire sur le résultat de la requête. Il faut que chaque enregistrement possède des enregistrements enfant

Exemple :
[SELECT Name
 FROM Position__c
 WHERE Id IN (SELECT Position__c FROM Job_Application__c)];

 
Anti-jointure gauche
L'anti-jointure permet de ne pas extraire les éléments pour lesquels il n'y a pas d'enregistrement enfant.

Exemple : 
[SELECT Name 
 FROM Position__c
 WHERE Id NOT IN (SELECT Position__c FROM Job_Application__c)];
Attention : Le nombre de tables enfant extraites est de 20 maximum par requête. Il est possible d'utiliser d'autres requêtes SOQL pour extraire les tables enfant restante, en faisant attention à la limite « Too many SOQL Queries ».


Le langage SOQL c'est tout ça, et bien plus encore grâce aux résultats globaux offrant COUNT, GROUP BY et autre HAVING, mais aussi grâce aux requêtes dynamiques et la généricité qu'elles apportent.

source: A Deeper look at SOQL and Relationship Queries on Force.com

mercredi 21 mars 2012

Les requêtes bulkées ou comment échapper au "Too many SOQL queries"

Elle laisse les novices dubitatifs et fait pleurer les plus expérimentés... Il s'agit de l'exception : Too many SOQL queries !

Qu'est ce c'est ?
Tout développeur Force.com qui a quelques heures de travail derrière lui, a déjà dû être confronté à ce fléau cette erreur au moins une fois.
Elle fait référence à la limite  "Total number of SOQL queries issued" fixée à 100 par Salesforce (voir Apex Governor Limits) qui empêche l'exécution d'un trop grand nombre de requêtes SOQL.

Pourquoi ?
Comptent pour une requête SOQL :
  • Chaque appel via l'instruction SELECT et ce peu importe sa complexité (champs enfants ou parents). Exemple :
Account myAccount = [SELECT Name FROM Account LIMIT 1];

  • Chaque appel dynamique via la méthode Database.query(). Exemple :
Account myAccount = (Account) Database.query('SELECT Name FROM Account LIMIT 1');
L'enjeu est donc de limiter ces appels dans le code. C'est d'autant plus vrai pour les triggers que pour les classes APEX car les requêtes contenues dans un trigger seront appelées autant de fois que celui-ci est déclenché.

Comment résoudre le problème ?
Il n'y a pas de solution miracle. Seul un code plus propre et plus respectueux des bonnes pratiques prônées par Salesforce permettra d'éviter d'atteindre la limite.

Parmi les bonnes pratiques, on peut retenir celle-ci (qui dépannera dans 99% des cas). Cette « Best Practice » conseille de ne pas envoyer de requêtes SOQL à l'intérieur d'une boucle (for ou while) mais plutôt de « bulker » (grouper) les requêtes en une seule.

Exemple :
Nous voulons lister les noms des contacts qui sont rattachés à un compte.

List<String> contactNames = new List<String>();
for (Account a : [SELECT Id FROM Account]) {
    for (Contact c : [SELECT Name FROM Contact WHERE AccountId=:a.Id]) {
        contactNames.add(c.Name);
    }
}

Le code ci-dessus soulèvera une exception SOQL si nous avons plus de 100 comptes dans le CRM.
Ceci peut être évité si nous utilisons plutôt le code qui suit.

Set<Id> accountIds = new Set<Id>(); 
for (Account a : [SELECT Id FROM Account]) {
     accountIds.add(a.Id);
}

List<String> contactNames = new List<String>(); 
for (Contact c : [SELECT Name FROM Contact WHERE AccountId IN :accountIds]) {
     contactNames.add(c.Name);
}

Si on gagne en lignes de codes, on gagne surtout en requêtes SOQL car on comptabilise maintenant 2 requêtes seulement et ce, indépendamment du nombre de comptes récupérés par la première requête.
Le code peut être encore amélioré en exploitant entièrement la puissance des requêtes SOQL, cela n'est pas le but de cet article mais fera sûrement l'objet d'un nouveau.