+ Start a Discussion
Matthew HofmannMatthew Hofmann 

Create and Associate Salesforce File Using Apex

Hello,

I'm creating an Apex class that creates a Salesforce File record and associates it with a parent record. I'm having a problem creating the association (ContentDocumentLink) record.

Here is what I'm doing:
  • Create a ContentVersion record. This creates a ContentDocument record automatically if you leave the ContentVersion.ContentDocumentId field blank. (https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_objects_contentdocument.htm - "To create a document, create a new version via the ContentVersion object without setting the ContentDocumentId. This automatically creates a parent document record.")
  • Insert ContentVersion record
  • DEBUG: Confirm ContentVersion.ContentDocumentId is set (Here is where the problem is because it is NULL; however if I SOQL query ContentVersion after the Apex completes, ContentVersion.ContentDocumentId is set and the corresponding ContentDocument record exists)
  • Create ContentDocumentLink record (commented out because it fails because ContentDocumentLink.ContentDocumentId is a required field)
  • Insert ContentDocumentLink record (commented out because it fails because ContentDocumentLink.ContentDocumentId is a required field)
Here is the code:
public class FileController {
    
    @AuraEnabled
    public static Id saveTheFile(Id parentId, String fileName, String base64Data, String contentType, Id contentDocumentId) { 
        base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');
        
        ContentVersion cv = new ContentVersion();
        cv.ContentLocation = 'S';
        cv.ContentDocumentId = contentDocumentId;
        cv.VersionData = EncodingUtil.base64Decode(base64Data);
        cv.Title = fileName;
        cv.PathOnClient = filename;
        
        insert cv;
        
        //***This DEBUG statement must return an Id for the rest of the code to work***
        System.debug('contentDocumentId='+cv.ContentDocumentId);               
        
        //ContentDocumentLink cdl = new ContentDocumentLink();
        //cdl.ContentDocumentId = cv.ContentDocumentId;
        //cdl.LinkedEntityId = parentId;
        //cdl.ShareType = 'I';
        
        //insert cdl;
        
        return cv.Id;
    }

    @AuraEnabled
    public static Id saveTheFile(Id parentId, String fileName, String base64Data, String contentType) {         
        return saveTheFile(parentId, fileName, base64Data, contentType, NULL);
    }
}

Does anyone know what I'm doing wrong, not considering, etc?
Appreciate any thoughts and input!
Best Answer chosen by Matthew Hofmann
VineetKumarVineetKumar
Interesting..

Well the culprit in your code is the null value and way you are using it to assign the value to ContentDocumentLink.contentDocumentId.
You should query the content version object again to get the contentDocumentId (which is generated after the parent document is inserted by default because of you passing of null value).
Something like below :
ContentDocumentLink cdl = new ContentDocumentLink();
cdl.ContentDocumentId = [SELECT Id, ContentDocumentId FROM ContentVersion WHERE Id =: cv.Id].ContentDocumentId;
cdl.LinkedEntityId = '00590000000a6dP';
cdl.ShareType = 'V';
insert cdl;

As per the salesforce documentation lines higlighted by you, "To create a document, create a new version via the ContentVersion object without setting the ContentDocumentId. This automatically creates a parent document record." it does create the parent document record as expected, but the contentDocumentId is still null in above initialised object. Id doesn't get updated automatically to the object instance. Infact you will have to pull out the corresponding Id as done in the above snippet.

Let me know if this helps.

All Answers

VineetKumarVineetKumar
What happens if you use the below in your code?
ContentDocumentLink cdl = new ContentDocumentLink();
cdl.ContentDocumentId = contentDocumentId;
cdl.LinkedEntityId = parentId;
cdl.ShareType = 'I';

 
Matthew HofmannMatthew Hofmann
Good question. Also I should have been more clear. In this case I'm calling the second method that does not have a contentDocumentId parameter, which in turn calls the first method so contentDocumentId is NULL. 
VineetKumarVineetKumar
Interesting..

Well the culprit in your code is the null value and way you are using it to assign the value to ContentDocumentLink.contentDocumentId.
You should query the content version object again to get the contentDocumentId (which is generated after the parent document is inserted by default because of you passing of null value).
Something like below :
ContentDocumentLink cdl = new ContentDocumentLink();
cdl.ContentDocumentId = [SELECT Id, ContentDocumentId FROM ContentVersion WHERE Id =: cv.Id].ContentDocumentId;
cdl.LinkedEntityId = '00590000000a6dP';
cdl.ShareType = 'V';
insert cdl;

As per the salesforce documentation lines higlighted by you, "To create a document, create a new version via the ContentVersion object without setting the ContentDocumentId. This automatically creates a parent document record." it does create the parent document record as expected, but the contentDocumentId is still null in above initialised object. Id doesn't get updated automatically to the object instance. Infact you will have to pull out the corresponding Id as done in the above snippet.

Let me know if this helps.
This was selected as the best answer
Matthew HofmannMatthew Hofmann
That worked. Thank you!

I still wonder why I wasn't able to access the cv.ContentDucmentId after insert cv; perhaps the automatic creation of the ContentDocument record happens asyncronously, in which case the SOQL query assignment you described may also fail sometimes. I should handle that possiblity in a try catch block.

Thanks again!
VineetKumarVineetKumar
No, the creation of ContentDocument record is not done asyncronously.
To explain the behaviour, take it this way. Since salesforce follows an MVC framework(Model = Object, View = VF pages, Controller = Apex Classes).
In your class i.e., the controller, send the request to the Model(database) to create ContentVersion record, the record gets created and an Id is generated and associated to it. ThisId creation and association is done at the Model (database) level only. Your Controller doesn't know what this Id is, so to get this Id we again make a query to the Model(database) level to extract it and use it.

I'm not sure if this made things more clear or confused you more.. :)
Nader Hadji GhanbariNader Hadji Ghanbari
Would have been great if the API returned the id of the automatically created ContentDocuemnt. In Apex it's kinda fine but for REST API it forces us to send an extra request to infer the ContentDocuemnt Id.