0

Following is my code :

trigger EmailValidation on Contact (before insert,before update,before delete) {
     
    // set to store emails present in the contacts that initiated this trigger
    set<String> emailSet = new set<String>();
    // set to store existing emails that matched emailSet
    set<String> existingEmails = new set<String>();
     
    // add all the emails of contacts in trigger to emailSet
    for(Contact con : Trigger.new) {
        emailSet.add(con.email);    
    }
    System.debug('EMAILSET '+emailset);
    // get the existing emails which match the emails in emailSet
    for(Contact con : [select Email from contact where email in : emailSet]) {
        
        existingEmails.add(con.Email);
    }
    System.debug('EXISTING EMAILS ---- ' + existingEmails);
    // for each contact in trigger.new
    for(contact a:trigger.new) {
        if(existingEmails.contains(a.Email)) {
            a.Email.adderror('This email already exists. Msg from trigger.');
        }
    }
    
    for(contact a:trigger.old){
        if(a.Phone != null){
            a.adderror('You cannot delete this account');  //EmailValidation: execution of BeforeInsert caused by: 
        }            //System.NullPointerException: Attempt to de-reference a null object Trigger.EmailValidation: line 26, column 1
    }
    
}
1
  • 3
    Welcome to SFSE. Please take a tour and visit help center & How to Ask. Post your question with relevant details for better responses and always try to refer to Salesforce developer guides to obvious problem & fixes before posting the question.
    – arut
    Commented Jul 1, 2022 at 5:24

2 Answers 2

4

Firstly, there's a few problems with your code that we should address first.

As you have specified multiple DML events that will invoke this trigger, those being

before insert,before update,before delete

So, this means that any time a Contact record is inserted, updated or deleted, this trigger will start to run its code, and it's going to try and run all of its code from line-by-line starting from the top right?

So, let's look at the for loop at the bottom

    for(contact a:trigger.old){
        if(a.Phone != null){
            a.adderror('You cannot delete this account');  //EmailValidation: execution of BeforeInsert caused by: 
        }            //System.NullPointerException: Attempt to de-reference a null object Trigger.EmailValidation: line 26, column 1
    }

If you insert a new Contact record, that's a DML event that's going to invoke the trigger in a before insert context, it's going to run all the code until the bottom where it's encountering this loop. Now it's trying to use Trigger.old, but this Contact was just inserted, so there isn't an 'old' version of it.

You can only use Trigger.old during the DML events:

before update, after update, before delete, after delete

Therefore, it is null because there's nothing in it during an insert DML event!

How can we fix your code then? (it is not advised to use any logic in triggers)

What we need to do is ensure each of these loops run only in the correct context.

YOUR NEW TRIGGER

trigger EmailValidationTrigger on Contact (before insert, before update, before delete) {
// Make object of trigger handler class. Tell class to execute code.
    EmailValidationTriggerHandler handler = new EmailValidationTriggerHandler();
    handler.execute();
}

YOUR TRIGGER HANDLER

public with sharing class EmailValidationTriggerHandler {
    public EmailValidationTriggerHandler() {}

    public void execute() {
// Use switch and call Trigger.operationType to get current context. Values in the switch are enums. It's like a shorter, nicer way of writing if-elseif-else as you can add more when clauses for all contexts.
        switch on Trigger.operationType {
// Call a method that contains logic for current context. (List<Contact>) makes the computer 'know' that Trigger.new is a list of Contacts instead of generic SObjects. This is called casting.
            when BEFORE_INSERT {beforeInsert((List<Contact>)Trigger.new);}
            when BEFORE_UPDATE {beforeUpdate((List<Contact>)Trigger.new, (Map<Id, Contact>)Trigger.oldMap);}
            when BEFORE_DELETE {beforeDelete((Map<Id, Contact>)Trigger.oldMap);}
        }
    }

    private void beforeInsert(List<Contact> contacts) {
        Map<String, Contact> emailContactMap = new Map<String, Contact>();
// Populate a map, exposing the Email address as a key
        for (Contact contact : contacts) {
            if (emailContactMap.get(contact.Email) == null) {
                emailContactMap.put(contact.Email, contact);
            } else {
// If we look in the map using this Contact's email address, but the map already contains data associated with that email address - it's a duplicate.
                contact.addError('Duplicate email address detected! A Contact record is already using this email address.');
            }
        }
// Pass the map of new Contacts' email addresses to a method, as before update needs to run the same code and it keeps this method cleaner
        checkDuplicateEmails(emailContactMap);
    }

    private void beforeUpdate(List<Contact> contacts, Map<Id, Contact> contactsOld) {
// This method runs in a before update context.
        Map<String, Contact> emailContactMap = new Map<String, Contact>();
        for (Contact contact : contacts) {
// If the Email field hasn't changed, we don't need to waste CPU time on checking if it has change to a duplicate address. We have a look at what the previous value was by grabbing this Contact from Trigger.oldMap using its ID which is the map's key.
            if (contact.Email != contactsOld.get(contact.Id).Email) {
                if (emailContactMap.get(contact.Email) == null) {
                    emailContactMap.put(contact.Email, contact);
                } else {
                    contact.addError('Duplicate email address detected! A Contact record is already using this email address.');
                }
            }
        }
// I would also guard this with an if to make sure the map has more than 0 values (just a tiny efficiency tip), I was not diligent enough to do so though
// if (emailContactMap.size() > 0) {checkDuplicateEmails(emailContactMap);}  <<like that
        checkDuplicateEmails(emailContactMap);
    }

    private void beforeDelete(Map<Id, Contact> contactsOld) {
        for (Contact contact : contactsOld.values()) {
            if (contact.Phone != null) {contact.addError('You cannot delete a Contact if the Phone field isn\'t empty!');}
        }
    }

    private void checkDuplicateEmails(Map<String, Contact> emailContactMap) {
// SOQL for loop to grab all the records matching an email address from the map
        for (Contact contact : [SELECT Id, Email FROM Contact WHERE Email IN :emailContactMap.keySet()]) {
// I was super sloppy here; The if statement isn't needed as the map can never be null (since the SOQL only returned Contacts that DO match an email address from the map)
            if (emailContactMap.get(contact.Email) != null) {
// Now, we're using one of these Contacts that are already on the system that match an email address from one of the new ones/updated ones
// We're using the email address to retrieve the Contact that we originally stored in the map so we can throw the error on it
             emailContactMap.get(contact.Email).addError('Duplicate email address detected! A Contact record is already using this email address.');
            }
        }
    }
}

I am pretty sure this is what you were trying to do. I'm quite new to coding too, so this may not be a good coding style to copy - but it should do what you were wanting.

Now that I've written this (I didn't try compile it, but I think I was careful enough), please don't blindly copy-paste it without trying to understand it.

If you need help, I am here and I will try guide you in the right direction. :)

2
  • I beginner of trigger so i don't understand full code please add comments on the code Commented Jul 2, 2022 at 5:30
  • I added some comments.
    – User
    Commented Jul 2, 2022 at 8:30
3

See the note about about trigger.old here

This sObject list is only available in update and delete triggers.

You are running into error during before insert execution because trigger.old will not be available for before insert. You will have to execute that for loop for update and delete triggers only.

Not the answer you're looking for? Browse other questions tagged .