1

I'm using MetatadaService class from https://raw.githubusercontent.com/financialforcedev/apex-mdapi/master/apex-mdapi/src/classes/MetadataService.cls, which I have updated to 53.0 version.

I'm trying to generate a field and add view/edit access to it for Standard User Profile.

The problem is: when I'm inserting FieldPermissions, I'm getting this error:

(Database.SaveResult[getErrors=(Database.Error[getFields=(Field);getMessage=Field Name: bad value for restricted picklist field: Opportunity.CUSTOM_FIELD_NAME__c;getStatusCode=INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST;]);getId=null;isSuccess=false;])

However, the field IS being created and if I run CustomFieldGenerator.addFieldToStandardProfile(); in a separate transaction, everything goes smoothly.

So my question is: do I really need to separate callout to Metadata API and DML for FieldPermissions, because SF won't see newly created field until the very end of transaction? Or am I missing something?

Very simplified code to test:

public class CustomFieldGenerator {
    public static final String LOCAL_NAMESPACE = [SELECT NamespacePrefix FROM Organization LIMIT 1].NamespacePrefix;
    public static final String CUSTOM_FIELD_NAME = (String.isNotBlank(LOCAL_NAMESPACE) ? (LOCAL_NAMESPACE + '__') : '') + 'CUSTOM_FIELD_NAME__c';
    public static final String STANDARD_OBJECT_NAME = 'Opportunity';
    
    private static MetadataService.CustomField generateField() {
        MetadataService.CustomField field = new MetadataService.CustomField();
        
        field.fullName = STANDARD_OBJECT_NAME + '.' + CUSTOM_FIELD_NAME;
        field.label = 'CUSTOM_FIELD_NAME';
        field.type_x = 'Text';
        field.length = 255;
        
        return field;
    }
    
    public static void createField() {
        MetadataService.MetadataPort service = new MetadataService.MetadataPort();
        service.SessionHeader = new MetadataService.SessionHeader_element();
        service.SessionHeader.sessionId = UserInfo.getSessionId();
        service.CallOptions = new MetadataService.CallOptions_element();
        service.timeout_x = 120000;
        
        List<MetadataService.SaveResult> saveResults = service.createMetadata(new List<MetadataService.CustomField>{
            generateField()
                });
        
        System.debug(saveResults);
    }
    
    public static void addFieldToStandardProfile() {
        Id profileBasedPermissionSetId = [SELECT Id FROM PermissionSet WHERE Profile.Name = 'Standard User'].Id;
        List<FieldPermissions> toInsert = new List<FieldPermissions>{new FieldPermissions(
            ParentId = profileBasedPermissionSetId,
            SobjectType = STANDARD_OBJECT_NAME,
            Field = STANDARD_OBJECT_NAME + '.' + CUSTOM_FIELD_NAME,
            PermissionsRead = true,
            PermissionsEdit = true)
            };
        List<Database.SaveResult> res = Database.insert(toInsert, false);
        System.debug(res);
    }
}

Run through Developer Console:

CustomFieldGenerator.createField();
CustomFieldGenerator.addFieldToStandardProfile();

Just to make it clear. I have moved to this approach from inserting field permissions through Metadata API, too, which included reading Profiles and updating them. But callout to Metadata API had already been seeing updated schema in the same transaction.

4
  • This might have something to do with a cached version of the schema in the same transaction. Might be a bug or might be working as designed. Have you tried introducing a delay between calls in the same transaction?
    – identigral
    Dec 6, 2021 at 17:25
  • No, I haven't, this whole thing was about fastening code and replace another callout to Metadata api (read and update profiles) with dml. But I will definitely try just to make sure. Thank you! @identigral Also, can you please tell me, how can I achieve time delay between callouts to websrvice and to rest api without using queueable or future, which will count as separate transaction? Dec 6, 2021 at 17:29
  • 1
    You can get 10 seconds by doing SELECT...FOR UPDATE twice - once on some randomly selected, pre-existing record and second time on the same record. The second call will block. An easier way might be to do another callout to some service under your control.
    – identigral
    Dec 6, 2021 at 17:46
  • Thank you! I was thinking about second approach, so will update question with additional information. Dec 6, 2021 at 18:16

1 Answer 1

0

Apparently, the answer is NO, you cannot add FieldPermissions in the same transaction with callout to Metadata API for creating custom field.

I have tried to add delay with callout to external service for 1 minute as @identigral suggested, but that didn't help. Even though the field actually appears in SF UI in the beginning of the transaction when it's still running. I will log a case with SF Support to know exactly why the Schema isn't updated right after callout to Metadata and if it is a designed behavior or a bug.

My answer will be updated. For now the solution is to separate callout to Metadata API and DML into different transactions.

UPDATE:

Answer from SF support:

Yes, the callout to metadata and DML for FieldPermission should be in different transactions.

... unfortunately, there is no documentation for this.

You must log in to answer this question.

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