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
MarkLevyMarkLevy 

Dumbfounded: APEX updating Custom Object -- Invalid id

I'm getting this error when trying to run through the test of a trigger on my new custom object:
 
System.DmlException: Update failed. First exception on row 0 with id a1O0S0000001QoIUAU; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, MasterBridgeApplicationTrigger: execution of BeforeUpdate

caused by: System.StringException: Invalid id: BRAPP00019

External entry point
Trigger.MasterBridgeApplicationTrigger: line 17, column 1: []
Stack Trace 

Class.BridgeApplicationProcessingTest.BridgeAppTest: line 44, column 1
My custom object has an autoincrement Name (as you see, BRAPP00019) -- Which seems to keep autoincrementing in my test even though I'm using @isTest(seeAllData=false) for my test -- which is odd.

But furthermore, why is updating the object in the database throwing this error? why is it confusing the Name with the Id when updating??

Here's the code I have that causes the error (error caused by the line: 'update app;')
Contact ourContact = [SELECT Id, Email FROM Contact WHERE Email = 'test1@nope.com'];

		Bridge_Application__c app = new Bridge_Application__c(
			Applicant__c = ourContact.Id
		);

		insert app;

		app = [SELECT Name, Id, Applicant__c,
		First_Decision__c,
		Second_Decision__c,
		Third_Decision__c,
		FROM Bridge_Application__c WHERE Applicant__c = :ourContact.Id];

		app.First_Decision__c= 'Admit';
		app.Second_Decision__c= 'Admit';
		app.Third_Decision__c= 'Admit';

		update app;

I'm completely dumbfounded here.

Why is APEX itself getting confused about what the Id is and what the Name is when it pulled all this information from the select statement that got the app in the first place?

Why is the Id auto incrementing every time I run through this test even though i'm using @isTest(seeAllData=false)?

Any help here is appreciated, I've been searching the forums heavily and can't find anything remotely like this problem.

Thanks!
Best Answer chosen by MarkLevy
Alain CabonAlain Cabon
If you post the code, you will get a solution at once.
if(oldApps != null){
	oldApp = oldApps.get(b.Name);				
}
should be (probably):
if(oldApps != null){
	oldApp = oldApps.get(b.Id);				
}

Not tested but Map<Id,Bridge_Application__c> oldApps;

Regards

All Answers

Alain CabonAlain Cabon
[SELECT <request>] returns a list by default in Apex without Ids.

The compiler can return one record but that seems unsecured if you don't limit the result.

Strengthen the unicity first (not the complete solution) and now look at the logs in detail (CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY is one of the most confusing error) with developer console opened:

Contact ourContact = [SELECT Id, Email FROM Contact WHERE Email = 'test1@nope.com' LIMIT 1];
system.debug('contact id :' + ourContact.Id);
app = [SELECT Name, Id, Applicant__c, First_Decision__c, Second_Decision__c, Third_Decision__c, FROM Bridge_Application__c WHERE Applicant__c = :ourContact.Id LIMIT 1];
 
Regards
MarkLevyMarkLevy
Thanks for the tip but I'm using a controlled test that is guaranteed to return 1 result anyway. I'm really looking for answers to my original question as to what in the world is going on here and why Salesforce is mixing up ID and Name fields of the Bridge_Application__c custom object.

Nevertheless I added the LIMIT 1 to the query, no change to the result. The debug info itself isn't particularly illuminating:
----------------------------------------------
09:44:12.76 (3323746405)|CODE_UNIT_FINISHED|MasterBridgeApplicationTrigger on Bridge_Application trigger event AfterInsert for [a1O0S0000001SZH] 09:44:12.76 (3325431826)|DML_END|[30] 09:44:12.76 (3325856411)|SOQL_EXECUTE_BEGIN|[32]|Aggregations:0|SELECT Name, Id, Applicant__c, First_Decision__c, First_Date_Updated__c, Second_Decision__c, Second_Date_Updated__c, Third_Decision__c, Third_Date_Updated__c FROM Bridge_Application__c WHERE Applicant__c = :tmpVar1 LIMIT 1 09:44:12.76 (3345371077)|SOQL_EXECUTE_END|[32]|Rows:1 09:44:12.76 (3345682520)|USER_DEBUG|[42]|DEBUG|Name: BRAPP00020, ID: a1O0S0000001SZHUA2 09:44:12.76 (3345731335)|DML_BEGIN|[44]|Op:Update|Type:Bridge_Application__c|Rows:1 09:44:12.76 (3401934019)|CODE_UNIT_STARTED|[EXTERNAL]|01q0S000000CkX1|MasterBridgeApplicationTrigger on Bridge_Application trigger event BeforeUpdate for [a1O0S0000001SZH] 09:44:12.76 (3402600262)|EXCEPTION_THROWN|[EXTERNAL]|System.StringException: Invalid id: BRAPP00020 09:44:12.76 (3402951346)|FATAL_ERROR|System.StringException: Invalid id: BRAPP00020 External entry point Trigger.MasterBridgeApplicationTrigger: line 17, column 1 09:44:12.76 (3402964681)|FATAL_ERROR|System.StringException: Invalid id: BRAPP00020
----------------------------------------------

I even pulled the name / ID out of the application object and it does show Name as name and Id as Id, but for some reason the "update" method is confusing them...
----------------------------------------------
09:44:12.76 (3345682520)|USER_DEBUG|[42]|DEBUG|Name: BRAPP00020, ID: a1O0S0000001SZHUA2
----------------------------------------------
Alain CabonAlain Cabon
Hi,

Post the complete trigger source code of Bridge_Application.

MasterBridgeApplicationTrigger on Bridge_Application trigger event AfterInsert for [a1O0S0000001SZH] 
Name: BRAPP00020, ID: a1O0S0000001SZHUA2 
MasterBridgeApplicationTrigger on Bridge_Application trigger event BeforeUpdate for [a1O0S0000001SZH] 
FATAL_ERROR|System.StringException: Invalid id: BRAPP00020 External entry point Trigger.MasterBridgeApplicationTrigger: line 17, column 1 09:44:12.76 (3402964681)|FATAL_ERROR|System.StringException: Invalid id: BRAPP00020

It seems that there is an error in the trigger Bridge_Application.

As soon as you will have post the complete trigger source code of Bridge_Application, many people here will understand at once the error in your code.

Regards
MarkLevyMarkLevy
Bridge_Application isn't a trigger, it's an object

Here is my master trigger for that object:
 
trigger MasterBridgeApplicationTrigger on Bridge_Application__c (
	before insert, after insert, 
	before update, after update, 
	before delete, after delete) {

	if(Trigger.isBefore) {
		if(Trigger.isInsert) {
			//BridgeApplicationProcessing
			BridgeApplicationProcessing BridgeApplicationProcessingTrigger =
				new BridgeApplicationProcessing(Trigger.oldMap, Trigger.new);
			BridgeApplicationProcessingTrigger.runTrigger();
		} 
		if(Trigger.isUpdate) {
			//BridgeApplicationProcessing
			BridgeApplicationProcessing BridgeApplicationProcessingTrigger =
				new BridgeApplicationProcessing(Trigger.oldMap, Trigger.new);
			BridgeApplicationProcessingTrigger.runTrigger();
		}
		if(Trigger.isDelete) {

		}
	}

	if (Trigger.IsAfter) {
		if (Trigger.isInsert) {

		}
		if(Trigger.isUpdate) {
			
		}
		if(Trigger.isDelete) {
			
		}
	}

}

the class called in insert / update is as follows:
 
public class BridgeApplicationProcessing {
	// These variables store Trigger.oldMap and Trigger.newMap
	Map<Id,Bridge_Application__c> oldApps;
	List<Bridge_Application__c> newApps;

	// This is the constructor
	// A map of the old and new records is expected as inputs
	public BridgeApplicationProcessing(
		Map<Id,Bridge_Application__c> oldTrigger, 
		List<Bridge_Application__c> newTrigger) {
			oldApps = oldTrigger;
			newApps = newTrigger;
	}
	
	// The one method your master trigger will call
	public void runTrigger() {

		if(newApps != null){
			for(Bridge_Application__c b : newApps){
				Bridge_Application__c oldApp = null;

				if(oldApps != null){
					oldApp = oldApps.get(b.Name);
				}

				//Insert or Update on decision or comments
				if((oldApp != null && (oldApp.First_Decision__c != b.First_Decision__c || oldApp.First_Comments__c != b.First_Comments__c))
					|| (oldApp == null && (b.First_Decision__c != null || b.First_Comments__c != null))){
					b.First_Date_Updated__c = datetime.now();
				}
				if((oldApp != null && (oldApp.Second_Decision__c != b.Second_Decision__c || oldApp.Second_Comments__c != b.Second_Comments__c))
					|| (oldApp == null && (b.Second_Decision__c != null || b.Second_Comments__c != null))){
					b.Second_Date_Updated__c = datetime.now();
				}
				if((oldApp != null && (oldApp.Third_Decision__c != b.Third_Decision__c || oldApp.Third_Comments__c != b.Third_Comments__c))
					|| (oldApp == null && (b.Third_Decision__c != null || b.Third_Comments__c != null))){
					b.Third_Date_Updated__c = datetime.now();
				}
				if((oldApp != null && oldApp.Open_Comments__c != b.Open_Comments__c)
					|| (oldApp == null && b.Open_Comments__c != null)){
					b.Open_Comments_Date_Updated__c = datetime.now();
					b.Open_Comments_Needs_Review__c = true;
				}
				if((oldApp != null && oldApp.Open_Comments_Date_Reviewed__c != b.Open_Comments_Date_Reviewed__c)
					|| (oldApp == null && b.Open_Comments_Date_Reviewed__c != null)){
					b.Open_Comments_Needs_Review__c = false;
				}
			}
		}
	}
}

 
Alain CabonAlain Cabon
If you post the code, you will get a solution at once.
if(oldApps != null){
	oldApp = oldApps.get(b.Name);				
}
should be (probably):
if(oldApps != null){
	oldApp = oldApps.get(b.Id);				
}

Not tested but Map<Id,Bridge_Application__c> oldApps;

Regards
This was selected as the best answer
MarkLevyMarkLevy
Oh boy I didn't notice that one! That does sound like it should fix it.. Let me see..

Yup.. That fixed it :)

It would be nice if the debugging gave more information as to where this errored exactly .. maybe that could be illuminated through a try/catch that displays further details about the error?
Alain CabonAlain Cabon
Sure. 

The main problem: Why does the compiler accept the mix of Id and String like here where it is obviously incorrect for standard fields like Id and Name? 

While your class is compiled (100% correct for the compiler), you will believe that all is fine for the types of the parameters like in java.

I am learning myself a lot solving this kind of strange problems here.
I had just the advantage of fresh eyes.