0

I have made this simple class to assist a flow to delete content documents, it is working just fine in my apex tests but when I invoke it from a flow I get the following error message: 13:35:33:092 EXCEPTION_THROWN [25]|System.DmlException: Delete failed. First exception on row 0 with id 0693N000001erIWQAY; first error: ENTITY_IS_DELETED, entity is deleted: []

If I check the Id in the error message it is very much present in the database, so it has not been deleted.

What is it I am doing wrong?

Apex class and test class below:


public class deleteCaseAttachments {
    @InvocableMethod(label='Delete ContentDocuments' description='Files assosiated with the case' category='Case')
    public static List<String> deleteFiles (List<ID> parentId){
        
        system.debug('deleteCaseAttachments.deleteFiles started. parent case id"s: '+parentId);
        List<ContentDocumentLink> contentDocumentLinks = [SELECT ContentDocumentId,LinkedEntityId FROM ContentDocumentLink WHERE LinkedEntityId = :parentId];
        system.debug('deleteCaseAttachments.deleteFiles contentDocumentLinks: '+contentDocumentLinks);
        List<Case> parentCaseList = [SELECT id, caseNumber,status,closedDate,Jira_level_3_case__c,Jira_status__c FROM case WHERE id=:parentId];
        system.debug('deleteCaseAttachments.deleteFiles parentCaseList: '+parentCaseList);
        List<ContentDocument> listToDelete = new List<ContentDocument>();
        
        for(Case parentCase:parentCaseList) {
            if(parentCase.status == 'CLOSED' ){
                if (contentDocumentLinks .size() > 0){
                    for (contentDocumentLink c: contentDocumentLinks){                      
                        if(c.LinkedEntityId == parentCase.id){
                            listToDelete .add(new ContentDocument(Id = c.ContentDocumentId));
                        }                   
                    }
                }
                system.debug('deleteCaseAttachments.deleteFiles listToDelete content: '+listToDelete);
                //for cases with a linked jira case
                if (parentCase.Jira_level_3_case__c != null && parentCase.Jira_status__c == 'ALL FIXES CLOSED'){
                    if(listToDelete .size() > 0)
                        delete listToDelete;                
                }
                //for cases with no link to any jira case
                else if (parentCase.Jira_level_3_case__c == null && parentCase.Jira_status__c == null || parentCase.Jira_level_3_case__c == '' && parentCase.Jira_status__c == ''){
                    if(listToDelete .size() > 0)
                        delete listToDelete;           
                } 
            } 
        }  
        return parentId; //just return something to the flow not used really
    }
}

Test class:


@isTest
private class testDeleteCaseAttachments {
    @isTest
    static void  deleteCaseAttachments(){
        Account acc = TestDataFactory.getAccount('test','SEK','Telco','Telecom Multiservice','EUR',true);         
        Contact con = TestDataFactory.getContact('Testare','Test','abcd','abcd','[email protected]',acc.Id,'Staff',true);
        User caseOwner = TestDataFactory.getUser('caseOwner.newIncidentTest');
        case theCase = TestDataFactory.getCase('Production',acc.id,con.id,false);
        case theCase2 = TestDataFactory.getCase('Production',acc.id,con.id,false);
 
        INSERT theCase;
        INSERT theCase2;
        contentVersion readMeFile = TestDataFactory.getContentFile('someFileName',theCase.id,false);
        contentVersion readMeFile2 = TestDataFactory.getContentFile('someFileName2',theCase2.id,false);
//      INSERT readMeFile;
//      INSERT readMeFile2;
        contentVersion readMeFile11 = TestDataFactory.getContentFile('someFileName11',theCase.id,false);
        contentVersion readMeFile22 = TestDataFactory.getContentFile('someFileName22',theCase2.id,false);
        INSERT readMeFile;
        INSERT readMeFile2;
        INSERT readMeFile11;
        INSERT readMeFile22;
        theCase.status = 'CLOSED';
        UPDATE theCase; 

        //boolean isDone=false; 
        
        List<String> incidentId = new List<String>();
        incidentId.add(theCase.id);
        incidentId.add(theCase2.id);
        List<ContentDocumentLink> contentDocumentLinksBeforeTest = [SELECT ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = :theCase.id];

        Test.startTest();  
        //no jira test
        deleteCaseAttachments.deleteFiles(incidentId);        
        string contentAfterTest;
        List<ContentDocumentLink> contentDocumentLinksAfterTest = [SELECT ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = :theCase.id];
        for(contentDocumentLink cdl : contentDocumentLinksAfterTest){
            contentAfterTest = cdl.ContentDocumentId;            
        }
        system.debug('testDeleteCaseAttachments.deleteCaseAttachments, contentVersion id after test, should be null: '+contentAfterTest);
        System.assertEquals(null, contentAfterTest);
        
        //has jira test, should be deleted
        contentVersion readMeFile3 = TestDataFactory.getContentFile('someFileName3',theCase.id,false);
        contentVersion readMeFile4 = TestDataFactory.getContentFile('someFileName4',theCase.id,false);
        INSERT readMeFile3;
        INSERT readMeFile4;
        theCase.Jira_level_3_case__c = 'a bogus jira link';
        theCase.Jira_status__c = 'ALL FIXES CLOSED';
        UPDATE theCase;
                
        deleteCaseAttachments.deleteFiles(incidentId);
        
        List<ContentDocumentLink> contentDocumentLinksAfterTest2 = [SELECT ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = :theCase.id];
        for(contentDocumentLink cdl : contentDocumentLinksAfterTest2){
            contentAfterTest = cdl.ContentDocumentId;            
        }
        system.debug('testDeleteCaseAttachments.deleteCaseAttachments, contentVersion id after test, should be null: '+contentAfterTest);
        System.assertEquals(null, contentAfterTest);
        
        //has jira test, should NOT be deleted
        contentVersion readMeFile5 = TestDataFactory.getContentFile('someFileName5',theCase.id,false);
        INSERT readMeFile5;
        theCase.Jira_level_3_case__c = 'a bogus jira link';
        theCase.Jira_status__c = 'IN PROGRES';
        UPDATE theCase;    
        
        deleteCaseAttachments.deleteFiles(incidentId);
        
        List<ContentDocumentLink> contentDocumentLinksAfterTest3 = [SELECT ContentDocumentId FROM ContentDocumentLink WHERE LinkedEntityId = :theCase.id];
        for(contentDocumentLink cdl : contentDocumentLinksAfterTest3){
            contentAfterTest = cdl.ContentDocumentId;            
        }
        system.debug('testDeleteCaseAttachments.deleteCaseAttachments, contentVersion id after test, should NOT be null: '+contentAfterTest);
        System.assertNotEquals(null, contentAfterTest);
        
        Test.stopTest();

    }

}
1
  • I guess the object still exists, because the transaction got aborted, because of the error. It seems that the error is not in your Apex, but in the place in which it is called in your flow. Can you please share a screenshot of your flow? Dec 2, 2021 at 14:48

1 Answer 1

2

ContentDocument may be linked with several cases at ones with contentDocumentLink. Imagine you have ContentDocument1 related to Case1 and Case2. You're iterating through Cases, you are on Case1, adding ContentDocument1 to listToDelete, deleting it. Then going to Case2, getting ContentDocument1 again, but it is already deleted in previous loop-iteration.

The solution is to store ContentDocument ids to Set<Id> contentDocumentIds. When you are moving out of Cases loop, go through your Set and create listToDelete there. (Also it is a bad practice to make DML inside for-loop).

By the way, you do not need if-statements on empty collections, because both in the way with DML and adding values to Set, if collection is empty they will not be performed.

And no need to check String variable both on null and empty String, it can be done with String.isBlank(...) function.

Code sample for better understanding:

Set<Id> contentDocumentsIdsToDelete = new Set<Id>();
List<ContentDocument> listToDelete = new List<ContentDocument>();

for (Case parentCase : parentCaseList) {
    if (parentCase.Status == 'CLOSED' ) {
        Set<Id> tmpIds = new Set<Id>();
        for (contentDocumentLink c: contentDocumentLinks) {
            if(c.LinkedEntityId == parentCase.id) {
                tmpIds.add(c.ContentDocumentId);
            }
        }
        System.debug('deleteCaseAttachments.deleteFiles listToDelete content: '+listToDelete);
        //for cases with a linked jira case
        if (parentCase.Jira_level_3_case__c != null && parentCase.Jira_status__c == 'ALL FIXES CLOSED'){
            contentDocumentsIdsToDelete.addAll(tmpIds);
        }
        //for cases with no link to any jira case
        else if (String.isBlank(parentCase.Jira_level_3_case__c) && String.isBlank(parentCase.Jira_status__c)) {
            contentDocumentsIdsToDelete.addAll(tmpIds);
        }
    }
}  

for (Id idToDelete : contentDocumentsIdsToDelete) {
    listToDelete.add(new ContentDocument(Id = idToDelete));
}

delete listToDelete;
5
  • N.B. You can make this code simpler by removing the three if statements that check collection.size() > 0 as the for loop and addAll will function just fine on empty collections
    – cropredy
    Dec 2, 2021 at 17:03
  • 1
    As well as in dml, by the way, apex knows if collection is empty and does not perform one. Dec 2, 2021 at 17:21
  • Thank you for your suggestions, I'll adjust my code and test again. Dec 6, 2021 at 8:19
  • I have adjusted my code, now it work just as intended Dec 13, 2021 at 7:30
  • Cool! So you may accept the answer if it worked for you, @DanielWretman Dec 13, 2021 at 7:55

You must log in to answer this question.

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