function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Matt CzugalaMatt Czugala 

Trigger for Contact works on before Insert, but not before Update

I am trying to create a trigger so that an error is given when a contact has a duplicate email address AND is not a partticular selection in a pick-list.  When the picklist value = User, Manager, or Exectuive.  If the Picklist value = None, we don't care.
Currently I have it working so that a new record cannot be created (before Insert), however am having trouble getting it to work so that if someone updates a contact an error is given.  Also when trying to correct a contact back to None, I receive the error, bascially not allowing me to fix the incorrect data.

Any suggestions?

trigger CheckDuplicateEmail on Contact (before insert, before update) {
   for (Contact c : Trigger.new){
      
      Contact[] contacts= [select id
                           from Contact 
                           where Email = :c.Email
                           AND elink_Contact_Type__c != 'None'
                           ];
      
      if (contacts.size() > 1 ) {
          c.Email.addError('A Duplicate contact be given a Login - use a different e-mail address or change their contact type');
     }     
   }
}


Best Answer chosen by Matt Czugala
bob_buzzardbob_buzzard
You aren't handling the situation where the contact in the trigger has a type of 'None', so even if you are changing that back, any existing record in the system with the same email address will flag up as an error.  You should exclude any records from the trigger that have a type of 'none', e.g.

trigger CheckDuplicateEmail on Contact (before insert, before update) {
   for (Contact c : Trigger.new){
      if (c.elink_Contact_Type__c != 'None')
      {
          Contact[] contacts= [select id
                               from Contact 
                               where Email = :c.Email
                               AND elink_Contact_Type__c != 'None'
                              ];
      
          if (contacts.size() > 1 ) {
              c.Email.addError('A Duplicate contact be given a Login - use a different e-mail address or change their contact type');
         }  
     }     
   }
}
I'm also duty bound to point out that your trigger isn't bulkified, as you have a SOQL query nested in the for loop, so if more than 100 records are loaded via the dataloader, for example, you will breach governor limits.  You can learn more about bulkification at:

https://developer.salesforce.com/page/Best_Practice%3A_Bulkify_Your_Code

All Answers

bob_buzzardbob_buzzard
You aren't handling the situation where the contact in the trigger has a type of 'None', so even if you are changing that back, any existing record in the system with the same email address will flag up as an error.  You should exclude any records from the trigger that have a type of 'none', e.g.

trigger CheckDuplicateEmail on Contact (before insert, before update) {
   for (Contact c : Trigger.new){
      if (c.elink_Contact_Type__c != 'None')
      {
          Contact[] contacts= [select id
                               from Contact 
                               where Email = :c.Email
                               AND elink_Contact_Type__c != 'None'
                              ];
      
          if (contacts.size() > 1 ) {
              c.Email.addError('A Duplicate contact be given a Login - use a different e-mail address or change their contact type');
         }  
     }     
   }
}
I'm also duty bound to point out that your trigger isn't bulkified, as you have a SOQL query nested in the for loop, so if more than 100 records are loaded via the dataloader, for example, you will breach governor limits.  You can learn more about bulkification at:

https://developer.salesforce.com/page/Best_Practice%3A_Bulkify_Your_Code

This was selected as the best answer
Avidev9Avidev9
Well first of all the code is not bulkified and will cause problem when records are being inserted in bulk from trigger / API. 

About the problem you are facing I think you may want to exclude the current record you are editing from the query ? something like

Contact[] contacts= [select id
                           from Contact 
                           where Email = :c.Email
                           AND elink_Contact_Type__c != 'None' AND Id != :c.Id
                           ];


Matt CzugalaMatt Czugala
Thank you for the suggestions.  This is only my second trigger, so I am still very new to salesforce development.  I will review your suggestions and test.
Matt CzugalaMatt Czugala
Bob,
I think your if statement I have added has gotten me closer, but there's something still off.
  • If I try to create a duplicate contact with login, I receive the error.
  • If I try to update an existing contact to have a login, I don't receive the error. (it's like it doesn't really find the existing duplicates)
  • If I try to update an existing contact that has a duplicated login, I receive the error and have to change the picklist back to none to save.
once I have this resolved I will look into the bulkification.
bob_buzzardbob_buzzard
This post is the first time you've mentioned login - is that something significant?

Point one sounds correct to me (assuming login doesn't mean something)
Points two and three seem conflcting - if you upate an existing contact you don't receive the error, and if you upate an existing contact you do receive an error?
bob_buzzardbob_buzzard
@Avidev9 makes a good point - the current record needs to be excluded from the search or it will show up as a false positive:

trigger CheckDuplicateEmail on Contact (before insert, before update) {
   for (Contact c : Trigger.new){
      if (c.elink_Contact_Type__c != 'None')
      {
          Contact[] contacts= [select id
                               from Contact 
                               where Email = :c.Email
                               AND elink_Contact_Type__c != 'None'
                               AND id!=:c.d
                              ];
      
          if (contacts.size() > 1 ) {
              c.Email.addError('A Duplicate contact be given a Login - use a different e-mail address or change their contact type');
         }  
     }     
   }
}


Matt CzugalaMatt Czugala
@bob_buzzard
"This post is the first time you've mentioned login - is that something significant?"

We define "login" by what value is in the picklist elink_Contact_Type__c and if they have an e-mail address.  If that picklist is anything other than None, they are generated a login for our website.  Our website generates the login to be the same as their e-mail address.  The options for that picklist are (None, User, Manager, Executive).  User/Manager/Executive options all generate a login, it's just they have different access levels.  If our website creates a duplicate login because of a duplicated e-mail address & both contacts are not set to NONE, neither accounts are able to login anymore.  I want to create this trigger so that our end users are not inadvertanly creating duplicate logins to our website.



Matt CzugalaMatt Czugala
I've added the suggestion from @Avidev9.  
Here is my testing process:  
I have 4 contacts in my sandbox.  Test None, Test User, Test Manager, Test Executive.  All of them have the elink_Contact_Type__c field set to None.  Each all have the same e-mail address (which is ok since all of them are set to "None").

Modify Test User, set elink_Contact_Type__c field to "User".  Save.  No Error (GOOD!)
Add new contact, set e-mail as duplicate.  Set elink_Contact_Type__C field to "User".  Save.  Error.  (GOOD!)
Modify Test Manager, set elink_Contact_Type__c field to "Manager".  Save.  (BAD!)  No Error.  I should receive an error since Test User has already set their field to User.



bob_buzzardbob_buzzard
I think I can see the problem here - in our excitement to correct the query, exclude the current records etc, we missed the >1 check at the end.  Now the query means that there is a record in the database that matches our email address, has a login and is not our record.  Thus the check should be > 0, as the first match is a duplicate.

trigger CheckDuplicateEmail on Contact (before insert, before update) {
   for (Contact c : Trigger.new){
      if (c.elink_Contact_Type__c != 'None')
      {
          Contact[] contacts= [select id
                               from Contact 
                               where Email = :c.Email
                               AND elink_Contact_Type__c != 'None'
                               AND id!=:c.id
                              ];
      
          if (contacts.size() > 0 ) {
              c.Email.addError('A Duplicate contact be given a Login - use a different e-mail address or change their contact type');
         }  
     }     
   }
}


Matt CzugalaMatt Czugala
Good catch, I did not notice myself.  I will test later this afternoon.  It's been a busy morning of meetings.
Matt CzugalaMatt Czugala
Looks like the 0 was the fix.  I did some searching/reading on bulkification.  I got a little bit confused here and there, but I ended up look at at blog and it seemed like all I needed to do was add a list line.  Did I miss something/oversimplify the bulkification?

trigger CheckDuplicateEmail on Contact (before insert, before update) {
    List<String> contactNames = new List<String>{};
   for (Contact c : Trigger.new){
       if (c.elink_Contact_Type__c != 'None'){
      	Contact[] contacts= [select id
                            From Contact 
                            where Email = :c.Email 
                            AND elink_Contact_Type__c != 'None'
                            AND id!=:c.Id
                            ];
            if (contacts.size() > 0 ) {
          c.Email.addError('A Duplicate contact be given a Login - use a different e-mail address or change their contact type');
          }
     }     
   }
}




bob_buzzardbob_buzzard
Its a bit more complex than that. To bulkify this you need to get the SOQL query outside the for loop, which probably means that you'll need to retrieve all contacts that match any of the email addresses in trigger.new and don't have a login type of 'None', then process the results against the trigger.new list to figure out if there are any duplicates.  Effectively it means you are matching the dupes rather than the database.  If you never plan to load data in through an automated tool you don't need to change anything.
Matt CzugalaMatt Czugala
I think I'm getting stuck with this bulkify practice.  

trigger CheckDuplicateEmail on Contact (before insert, before update) {
    List<Contact> lstcontact = new List<Contact>{};
    List<Contact> contacts = [select id from Contact where id in: trigger.newmap.keyset()
                             and elink_contact_Type__c != 'None'];
    for (Contact newcontact:contacts){     
       if (contacts.size() > 0 ) {
          newcontact.Email.addError('A Duplicate contact be given a Login - use a different e-mail address or change their contact type');
          }     
   }
}


bob_buzzardbob_buzzard
Your query is incorrect now - all you are doing is retrieving the contacts that you already have in the trigger, whereas you should built a list of the email addresses of contacts who have a login from the trigger, and then select back all contacts that aren't in the trigger, do have a login and match any of the email addresses.  Then you'd need to iterate the results and mark up the matching records from the trigger.
Matt CzugalaMatt Czugala
After spending quite a bit of time on this I have it working.  Thanks for all the help and suggestions.
Matt CzugalaMatt Czugala
I received this email alert.  Is this telling my bulkification is not working correctly?

Apex script unhandled trigger exception by user/organization: 00530000000cqnd/00D300000000L0G

CheckDuplicateEmail: System.LimitException: Too many SOQL queries: 101
bob_buzzardbob_buzzard
That's exactly what it means I'm afraid. The governor limit for SOQL queries per transaction is 100, and your transaction has gone over that.  Typically that means there is a SOQL query inside a loop (which may be in a different trigger to yours).
Matt CzugalaMatt Czugala
This is my latest attempt, I'm not sure what I'm missing.

trigger CheckDuplicateEmail on Contact (before insert, before update) {
 Map<string,Contact> mapcontact = new Map<string,Contact>{};
 Map<string,Contact> mapcontactupdate = new Map<string,Contact>{};
 for(Contact Con : trigger.new){
  if(Con.elink_contact_Type__c != 'None' && Con.Email != null)
  {mapcontact.put(Con.email,Con);}
 }
 if(System.Trigger.isInsert){
  for (Contact newcontact : [select id,email from Contact where email in: mapcontact.keyset() and elink_contact_Type__c != 'None']){   
   Contact oldcontact = mapcontact.get(newcontact.email); 
   if (oldcontact != null) {
      oldcontact.addError('A Duplicate contact be given a Login - use a different e-mail address or change their contact type');
       }
  }
 }
 if(System.Trigger.isUpdate)
 {
  for(Contact Con : trigger.new){
   if(Con.elink_contact_Type__c != 'None'){ Integer ContactCou = [select count() from Contact where email =: con.email];
    if(ContactCou > 1)
    {Con.addError('A Duplicate contact be given a Login - use a different e-mail address or change their contact type');} 
    }}}       
}