Работа в с Map в Apex

Map выступает типом данных, который сильно облегчит читаемость, эффективность и расширяемость вашего кода. Может быть применён множеством способов.

Надеюсь, что вы узнаете что-то новое, прочитав эту статью.

Инициализация Map

Тип Map имеет свою магию которая сделает ваш код более эффективным и читаемым.
Начнём со стандартного способа создания Map:

Map<Id, Account> accountsById = new Map<Id, Account>();

После создания мапы вы можете добавить значения с помощью метода put(). Однако, если у вас уже имеется List объектов, то можно просто передать коллекцию в конструктор.

Map<Id, Account> accountsById = new Map<Id, Account>(listOfAccounts);

Прелесть такого подхода в том, что он позволяет вам избежать итерации в цикле через весь лист. После передачи листа объектов Apex автоматически создаст набор ключей содержащих Id аккаунтов и сам аккаунт в качестве значения.

Такой подход поможет в случае, когда у нас есть триггер на аккаунте и нам нужно запросить для них контакты. Обычно приходиться идти через все аккаунты из Trigger.New и добавлять их id в Set. Но есть способ этого избежать:

Map<Id, Account> accountsById = new Map<Id, Account>(trigger.new);
Set<Id> accountIds = accountsById.keySet();

List<Contact> contacts = [SELECT Id, FirstName, LastName FROM Contact WHERE AccountId IN :accountIds];

Подсказка: если у вас есть лист объектов, то вам не нужно идти через всех чтобы получить id. Просто передайте этот лист объектов в запрос. Например у вас есть:

List <Account> accounts

И запрос будет иметь такой вид:

[SELECT Id FROM Contact WHERE AccountId IN :accounts]

Но что, если у вас нет листа с объектами для инициализации…
Тогда можно передать запрос сразу в map:

Map<Id, Account> accountsById = new Map<Id, Account>([SELECT Name FROM Account]);

В другом случае у вас может быть набор статичных переменных, которые нужно передать в map:

Map<string, string> stringMap = new Map<string, string>{ 'Key1' => 'Value1', 'Key2' =>  val2, key3 => val3};

Подход для запроса и хранения дочерних записей

Представим, что вам нужно написать триггер, который записывает некоторые значения в поля Opportunity, основываясь на значения, найденных в OpportunityLineItem

Теперь, с применением map, можно запросить все related записи для текущего листа opportunity. Затем организовать их в Map, где ключом будет opportunity Id и значением лист opportunity lime item, которые ассоциируются с этим opportunity Id.
Это будет иметь такой вид:

Set<Id> oppIds = trigger.newMap.keySet();
Map<Id, List<OpportunityLineItem>> lineItemsByOppId = Map<Id, List<OpportunityLineItem>>();

List<OpportunityLineItem> lineItems = queryOppLineItems(oppIds);  
for (OpportunityLineItem lineItem : lineItems) {  
    if (lineItemsByOppId.containsKey(lineItem.OpportunityId)) {
        lineItemsByOppId.get(lineItem.OpportunityId).add(lineItem);
    }
    else {
        lineItemsByOppId.put(lineItem.OpportunityId, new List<OpportunityLineItem>{ lineItem });
    }
}

for (Opportunity opp : trigger.new) {  
    if (lineItemsByOppId.containsKey(opp.Id)) {
        List<OpportunityLineItem> oppLineItems = lineItemsByOppId.get(opp.Id);

        for (OpportunityLineItem lineItem : oppLineItems) {
            /*Do Something*/
        }
    }
}

Организация данных

Например, если ваша организация использует множество Record Type, и вам нужен доступ к ним, чтобы выполнять определённую логику. Вы можете просто запросить все типы и идти через цикл определять для каждой записи свои действия, основываясь на типе. Но что, если вам нужно сделать это более чем в методе или классе.

Используя такой подход, вы сможете хранить record type и использовать их в более удобной форме. Сделав всего 1 запрос, появиться возможность использовать эти записи в множестве блоков кода в течении 1 транзакции.

Map<string, Map<string, RecordType>> recordTypesByTypeAndName = new Map<string, Map<string, RecordType>>();

List<RecordType> recordTypes = [SELECT Id, Name, DeveloperName, SObjectType, IsActive FROM RecordType];

for (RecordType rt : recordTypes) {
    if (recordTypesByTypeAndName.containsKey(rt.SObjectType)) {
        Map<string, RecordType> recordTypeByName = recordTypesByTypeAndName.get(rt.SObjectType);

        if (recordTypeByName.containsKey(rt.DeveloperName) == false) {
            recordTypeByName.put(rt.DeveloperName, rt);
        }
    }
    else {
        recordTypesByTypeAndName.put(rt.SObjectType, new Map<string, RecordType>{ rt.DeveloperName => rt });
    }
}

Ну и потом можно сделать helper метод, который поможет облегчить доступ к этим записям.

public Id getRecordTypeId(string objectType, string name) {
    Id recordTypeId = null;

    if (recordTypesByTypeAndName.containsKey(objectType)) {
        Map<string, RecordType> recordTypesByName = recordTypesByTypeAndName.get(objectType);
        if (recordTypesByName.containsKey(name)) {
            RecordType rt = recordTypesByName.get(name);
            recordTypeId = rt.Id;
        }
    }

    return recordTypeId;
}

Надеюсь. Что статья была полезна! Пишите ваши вопросы и отзывы :slight_smile:

5 Likes