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
qsdunnqsdunn 

After Insert trigger and deletion confusion

Problem in a nutshell:

 

This help page informs me that it is possible to delete an object in a trigger that's called after its insertion ("Allowed, but unnecessary. The object is deleted immediately after being inserted." )

 

However, that same page informs that it is *not* possible ("trigger.new and trigger.old cannot be used in Apex DML operations", "You cannot delete trigger.new" ).

 

When I try to run a trigger which does this, I get the following error message:

 

"Apex trigger createCustomEventTrigger caused an unexpected exception, contact your administrator: createCustomEventTrigger: execution of AfterInsert caused by: System.SObjectException: DML statment cannot operate on trigger.new or trigger.old: Trigger.createCustomEventTrigger: line 5, column 13"

 

 

Is it possible to use triggers to delete an object immediately after inserting it? And if so, how?

 

 

 

 

For those interest in a more detailed account of the problem: 

 

I'm currently trying to write a trigger and class so that whenever a user creates an Event (the standard one)there will be an Events__c custom object automatically created and populated with the same data and the "Related To" field of the standard Event object will be set to point to the custom Event object.

(Note, I've already written code that goes in the other direction and creates a standard Event when a custom Event is created. And yes, I'm prepared to prevent infinite loops.)

 

The problem I'm facing is getting all the necessary stages in the right order. If you can bear with me, I'll outline the different approaches I've tried, just in case one of the earlier ones has something in it that can be fixed:

 

 

I tried using a "before insert" trigger to create the new custom Event, insert it, query for it and put its ID in the standard Event's "Related To" field.

But I couldn't guarantee getting back the single, correct custom Event from my query because it didn't have any sort of unique identifying value in it.

 

So I tried using an "after insert" trigger so that the standard Event would have been assigned a Salesforce ID. I could put this ID in a hidden field when I created the custom Event, then insert it and query for it on that field, and get the custom Event's ID.

But then I can't put that ID into the standard Event because I can't modify it in an "after" trigger.

 

 

This left me with a chicken-and-egg problem, where the standard and custom events each had to be inserted before the other.

Trying to get out of it led me to my current attemplt at a solution: an "after insert" trigger on the standard Event creates the custom Event but doesn't immediately set the flag I use to prevent infinite recursion. This means that the trigger on the custom Event then creates a second standard Event with all the same data and with the "Related To" field pointing at the custom Event. All that's left to do is to delete the original standard Event and there's no need for the user to know that the Event they're looking at on the calender isn't the same one they just created.

qsdunnqsdunn

I've got something that seems to do what I want it to but it seems like a bit of an ugly hack and I'd still appreciate anyone's advice.

 

Currently, I'm creating a dummy object (of a kind that exists only for this purpose) with an external ID that I've hard-coded in. This object gets inserted into the database, then retrieved using this external ID (I know what it is because it's the same every time), then I create the custom Event object with the ID of the dummy object in a hidden field, retrieve the custom Event by querying this hidden field, and finally put the ID of the custom Event in the "Related To" field of the original standard Event. Oh, and then I delete the dummy object, so that the hard-coded external ID can be used again.

 

It's roundabout, it involves creating an entire database object just to generate IDs and it involves about twice as many DML calls as I would like. But it seems to work.

 

Anyone want to suggest something better?

mtbclimbermtbclimber

If you need to create an object against which the whatId on event needs to have the id value set then your first inclination to use a before trigger was correct. Without an Id you can't correlate back explicitly but you can implicitly by depending on the order of the list returned from Trigger.new and that of your result to insert the custom event object.

 

To answer your question, yes you *can* delete the records being inserted in an after insert trigger but it is discouraged if for no other reason that the user experience. This is how you do it:

 

 

trigger deletethese on Lead (after insert) {
Database.delete(Trigger.new);
}

 

The error you described seems to indicate this would throw an exception or compilation failure but neither was the case for me in my basic test.  Not sure what you are doing that is different from the above.

 

We'll also review the documentation to make sure it's clear.

 

Thanks,

 

qsdunnqsdunn

Would you believe it was as simple as using the DML statement syntax when I should have been using database method syntax?

I'm still not entirely sure *why* "Database.delete(x)" works and "delete x" doesn't...

 

But it's a moot point now, really. I can, as you say, get the IDs to use in a before trigger by looking at the returned result from the insert. 

I didn't think of that before because I was using the DML syntax for the insert too, so I didn't even realise that I could get a returned value from it...

mtbclimbermtbclimber

We're fixing this inconsistency and updating the docs. Thanks for bringing this to our attention and apologies for the confusion this may have caused.

 

By the way, just so you know when you insert records with a dml statement or with a database method we always update the rows passed in with the ID.  This test should highlight what I mean:

 

 

@IsTestprivate class ExampleTests {

static testmethod void verifyIdSetOnObject() {

Account a = new Account(name = 'new account (method)');

Database.insert(a);

System.assert(a.id != null);

 

Account b = new Account(name = 'new account (statement)');

insert b;

System.assert(b.id != null);

}

}

 

 

 

Message Edited by mtbclimber on 03-02-2010 09:14 PM
DianeMDianeM

I think I followed the example here exactly but got the following error.

 

 Unable to complete ship confirmation[Upsert failed. First exception on row 1; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, InvoiceLineAfterInsert: execution of AfterInsert caused by: System.SObjectException: DML statement cannot operate on trigger.new or trigger.old Trigger.InvoiceLineAfterInsert: line 7, column 4: []]

 

I am not deleting all objects in the after trigger - only those that meet a specific criteria.  I am adding the code to customize a managed package.  So I check the objects and the ones that meet the criteria are add to a list and then I use the Database.delete to delete them and get the error above.

 

Any ideas?

 

Diane

qsdunnqsdunn

Context Variable Considerations

 

Points to note:

-In an After Insert trigger, the objects in trigger.new have already been inserted into the database; thats what After Insert means. If you want to change what is inserted, you should do it in a Before Insert trigger.

-Trigger.old holds the values from the database before the effects of the trigger... which means that in an Insert trigger there are no values in trigger.old because the objects did not exist in the database before they were inserted into it.

 

 

My suggestion, use a Before Insert trigger, check the values in trigger.new against your criteria and remove the ones which don't fit from trigger.new. When the insert happens only the objects which were not deleted from trigger.new will be inserted.

You don't need to use trigger.old for anything.

TLFTLF

I tried using both the "delete Trigger.new;" and "Database.delete(Trigger.new);" options and in both cases I got an error similar to the following:

 

 Unable to complete ship confirmation[Upsert failed. First exception on row 1; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, InvoiceLineAfterInsert: execution of AfterInsert caused by: System.SObjectException: DML statement cannot operate on trigger.new or trigger.old Trigger.InvoiceLineAfterInsert: line 7, column 4: []]

Maybe using "Database.delete(Trigger.new)" worked back in March of 2010, when it was marked as a solution, but it doesn't work now.

 

I also tried the suggestion of doing this in a "before insert" trigger, and then trying to remove the unneeded items from the Trigger.new collection to prevent them from being inserted. This doesn't work either, because Trigger.new is a read-only collection.

 

I got this to work with the combination of an "after insert" trigger, and a future method that deletes the records that I don't want to keep. The future method (contained in a utilities class) looks like this:

 

	@future 
	public static void deleteSObjects(Set<Id> idSet, String objType) {
		List<SObject> objectList = new List<SObject>();
		for (Id sid : idSet) {
			sObject obj = Schema.getGlobalDescribe().get(objType).newSObject(sid);
			objectList.add(obj);
		}
		delete objectList;
	}

The "after insert" trigger calls this method as follows, once it is done processing the objects that fired the trigger:

 

MyUtils.deleteSObjects(Trigger.new.keySet(), 'LeadProxy__c');

 

TLFTLF

That was supposed to be in my previous post:

 

MyUtils.deleteSObjects(Trigger.newMap.keySet(), 'LeadProxy__c');

 

qsdunnqsdunn

TLF wrote:

Maybe using "Database.delete(Trigger.new)" worked back in March of 2010, when it was marked as a solution, but it doesn't work now.

 

I also tried the suggestion of doing this in a "before insert" trigger, and then trying to remove the unneeded items from the Trigger.new collection to prevent them from being inserted. This doesn't work either, because Trigger.new is a read-only collection.



You're absolutely correct: I used mtbclimber's suggestion of getting the ID back implicitly to solve my problem without actually having to delete anything; and my suggestion to DianeM was untested and flawed.

 

Your proposed method (creating "different" objects with the same IDs to delete objects from trigger.new without directly touching trigger.new) looks like it should work.

Although when I try to test it myself I'm inexplicably told "Compile Error: Method does not exist or incorrect signature: [Schema.SObjectType].newSObject​(Id)"...

Hard-coding in the correct type of SObject works fine.

TLFTLF

Hmm... I double checked the code that I posted and it is an exact copy of code that compiles and runs in my environment. Are you using the Force.com IDE, or the Salesforce UI to edit and save the class? I wonder if it is a API version issue. What is the API version on your class file?

qsdunnqsdunn

Salesforce UI and v22. I've just tried v23 and that didn't help.

 

I should be clear: my problem here is solved; I tried this code because (after getting called out for giving poor advice to DianeM) I didn't want to just say "It looks like it should work" and leave it at that.