Требование к триггеру: до вставки(insert) записей проверить, есть ли запись(и) с таким Passport Number, и если есть запретить вставку записи.
Реализация триггера для 1 записи:
trigger CarOwnersTrigger on Car_Owner__c (before insert) {
List<Car_Owner__c> carOwnerList = [SELECT Passport_Number__c FROM Car_Owner__c WHERE Passport_Number__c =: Trigger.new[0].Passport_Number__c];
if(!carOwnerList.isEmpty()) {
Trigger.new[0].addError('TRIGGER: Incorrect data');
}
}
Я хочу переделать так, чтобы триггер срабатывал если записи вставляются списком(List).
Вопросы:
1. Где можно так почитать, чтобы разобраться в Bulk Apex Triggers (Trailhead я уже читал, разобраться с этим вопросом там сложно)?
2. Как это правильно сделать? Нужно ли делать List<List<Car_Owner__c>> и затем циклом перебирать по внешнему списку?
trigger CarOwnersTrigger on Car_Owner__c (before insert) {
List<Car_Owner__c> availablePassports = [SELECT Passport_Number__c FROM Car_Owner__c];
for(Car_Owner__c coNew: Trigger.New) {
for(Car_Owner__c coAva: availablePassports) {
if(coNew.Passport_Number__c == coAva.Passport_Number__c) {
coNew.addError('TRIGGER: Incorrect data');
}
}
}
}
можно сделать так(говнокодец, так сказать)
trigger CarOwnersTrigger on Car_Owner__c (before insert) {
Set<String> uniquePassportNumbers = new Set<String>();
for(Car_Owner__c o: [SELECT Passport_Number__c FROM Car_Owner__c]) {
if (String.isNotBlank(o.Passport_Number__c)) {
uniquePassportNumbers.add(o.Passport_Number__c);
}
}
for(Car_Owner__c n: Trigger.New) {
if (String.isNotBlank(n.Passport_Number__c) && uniquePassportNumbers.contains(n.Passport_Number__c)) {
n.addError('TRIGGER: Incorrect data');
}
}
}да и в принципе, проверять на уникальность такой записи не имеет смысла в триггере
имеет смыслсделать поле Passport_Number__c уникальным
Именно так и нужно.
Делать такое нельзя
for(Car_Owner__c o: [SELECT Passport_Number__c FROM Car_Owner__c]) {
там стоит пометка - говнокодец :)
только если собирать строку руками : WHERE PassportNUmber__c = '1' OR '2' и тд
WHERE PassportNUmber__c IN :listOfString
?
Балкификация АПЕКС тригеров - это базовая тема, достойная того, чтобы ее погуглить и изучить.
дело в том, что тригеры делаются не для простых бизнес-логик случаев, а для более сложных, там где, например, нужно что-то покверить на другом объекте или чтобы что создать новое (впрочем последнее можно уже делать и с П Билдером).
и все бы ничего, но записи могут прийти в тригер пачками вплоть до 200 шт, и если для каждой записи ты будет кверить нужную инфу отдельно, то быстро стукнешь лимиты.
поэтому действовать прямолинейно здесь не получится, и приходится кверить все требуемым данные одним-несколькими объединенными запросами, а не для каждой записи пришедшей в тригер отдельно.
то есть вначале проходишься циклом по пришедшим записям и собираешь инфу о том, что и как кверить.
потом кверишь, и создаешь Мэпы (если там несколько объектов включено), связывающие эти записи (иначе как ты поймешь, какай инфа к чему принадлежит?)
затем в основном цикле по пришедшим записям ты выполняешь требуемую бизнес-логику, использую те самые Мэпы.
и если создаются новые записи, или апдатируются записи не являющиеся пришедшими в тригер - то такие записи собираются в Листы, которые после цикла создаются или апдатируются одной ДМЛ операцией (а не многими, если бы это делалось в цикле)
вот и все.
есть много нюансов, например с тестами. может так случится, что подготовка требуемых данных работает правильно только в случае, если в тригер пришла одна запись, а вот если там пришло несколько - то там баг (неправильно Мапы создаются). И если твой тест проверяет правильность выполнения логики только на одной записи, то этот баг может остаться незамеченным. Нужно тестить логику на нескольких записей, отправляемых в тригер одновременно. Да, обычно тригеры тестятся на 200 записей, это помогает проверить лимиты, но не факт что такой балк-тест проверяет еще и логику (так как это для этого нужно создавать разные требуемые данные для разных записей)
я думаю, что в твоем случае, тригер простой, так как там нет сложной подготовки данных.
(1)собираешь в цикле пришедшие паспорта
(2) кверишь существующие записи по паспортам
(3) если ничего не выкверилось, то все свободны.
а вот дальше интереснее. Если тригер работает в все-или-ничего режиме и n.addError вызывает полный откат, то зачем вообще выяснять, какая запись имеет проблему, ведь все равно ни одна не будет создана?
но все же лучше пройтись по списку пришедших записей и повесить n.addError на вызвавшую проблему, для удобства пользователя
а если там несколько проблемных записей, то я не знаю можно ли с помощью n.addError собрать несколько проблем, или в мессадж уходит только первая или последняя проблема, или всеже таким образом можно собрать инфу по всем проблемным записям? если нет, то придется сначала собирать проблемы, а потом выводить их одним n.addError
и вот что совсем интересно. А работает ли тригер во все-или-ничего режиме в случае с мульти-записями? скорее всего так и есть, я не помню. но если так и есть, то как быть если требования такие: из всех пришедших на инсерт записей, выбрать БЕЗпроблемные и создать их, а другие НЕ создавать, выяснить в чем проблема, и дать знать пользователю с какой записью и в чем проблема. Т.е. вопрос в том как выборочно ПРЕДОТВРАТИТЬ создание записи в бефо инсерт тригере, не создав проблем другим создаваемым записям?
стринг так не работает :)
че? реально? Пошел проверять ![]()
У меня работает
List<String> l = new List<String>{
'Apex CPU time limit exceeded',
'invalid ID field: null'
};List<Log__c> logs = [SELECT Id FROM Log__c WHERE Summary__c IN :l];
SYSTEM.DEBUG('XXXXX: '+logs.size());
XXXXX: 10
Пойду и я перепроверю)
Пару недель назад это все еще не работало
UPD: действительно работает, надо будет зарефакторить весь проект, потому что много мест где наши бэкендщики(не я) собирали строку xD
Да оно всю жизнь работает. Сколько себя помню использовал эту конструкцию ![]()
Оно не работает если у тебя филд Long Text или Rich Text
а я вот у нас в проекте не помню такого вообще, все всегда собирали полностью строку
вот и сижу и думаю - какого хера)
Может у вас используют поделки в роде fflib Query Builder а не SOQL напрямую?
Поэтому и строят SOQL динамически.
ХОТЯ даже в динамический SOQL можно передать :listOfString и он должен подхватить сам лист.
у нас много всякого говна бесполезного xD