I was trying out a simple trigger on Case Object.There is a field Hiring_Manager__c (looks up to User) in Case .On update or insert of a case, this field has to be populated with the Case Owner's Manager.I created the trigger as follows.It is not bulkified as I was just trying out for a single record.
I could see the value getting populated correctly in debug statements.But it is not updated on the record.
trigger HiringManagerupdate_case on Case (before insert,before update) {
Case updatedCaseRec= [select id,ownerId,Hiring_Manager__c,CaseNumber from case where id in :Trigger.new];
system.debug('Case Number'+updatedCaseRec.CaseNumber);
system.debug('Manager before updation'+updatedCaseRec.Hiring_Manager__c);
try{
User caseOwner = [Select Id,Name, ManagerId, Manager.Email From User Where Id = :updatedCaseRec.ownerId];
updatedCaseRec.Hiring_Manager__c=caseOwner.ManagerId;
}
Catch(DMLException de){
system.debug('Could not find Manager');
}
system.debug('Manager'+updatedCaseRec.Hiring_Manager__c);
}
I got a reply for this in developer's forum with a code that works fine.
trigger HiringManagerupdate_case on Case (before insert,before update) {
for(Case cas :Trigger.new){
try{
User caseOwner = [Select Id,Name, ManagerId, Manager.Email From User Where Id = :cas.ownerId];
cas.Hiring_Manager__c=caseOwner.ManagerId;
}
Catch(DMLException de){
system.debug('Could not find Manager');
}
system.debug('Manager'+cas.Hiring_Manager__c);
}
I would like to know
1)Difference in logic in both the approaches if I am trying to update only a single record?Mine is populating the value in debug statement but not on screen.
2)Will the select statement inside the loop (of second approach)affect governor limit?
3)Do we need to explicitly call update for before triggers? My understanding is the save and commit happens after the field update for before triggers and hence the changes will be saved automatically without calling update statement.?
Thank in advance.
Nisha
1) If you are trying to access the record(s) for the object which fired a before trigger, you must iterate over Trigger.new. You cannot query for it because the record(s) may not be in the database yet.
2) Yes it could. The query executes for every Case record being inserted or updated. If you have 1,000 such Cases, the query executes a 1,000 times.
3) No, we do not need to call update for before triggers. Your understanding is correct.
Related
Well, Sorry, if you find this question weird, But Let me ask It anyway.
Imagine the following situation. There is 2 Clients, A and B. The A Client decided to create Profile and the Transaction in general takes 2 Minutes until Completion for example.
After 1 minute, B Client Decided to create a Profile with THE SAME Username And Password, (but the first Transaction is still in the Process, And we cannot apply the unique constraint, because there is no such User with this Username quite yet.)
So It will eventually end up with UNIQUE CONSTRAINT exception, and we'll need to make a rollback.
The Question is: How to avoid this situation?
I've heard about LOCK in PostgreSQL (that allows to lock the EXISTING ROW in order to others can't change it or read) but haven't find any similar to this sort of case.
Is there any feature, that provides some sort of functionality to block potential transactions?
Start the transaction like this:
BEGIN;
SET lock_timeout = 1;
INSERT INTO users (username, password) VALUES (...);
RESET lock_timeout;
/* the rest of the transaction */
COMMIT;
The second transaction that tries to create the same user won't block, but fail right away and can be rolled back.
EDIT: in case someone else stumbles across this, Laurenz Albe's post is a better solution. Use that instead.
The Question is: How to avoid this situation?
A simple way would be to split the commit into two parts, probably using a savepoint:
func createUser(user: User) error {
this.db.exec('INSERT INTO users VALUES ($1, $2)', user.username, user.hashedPassword);
this.db.withTransaction(func (tx Transaction) {
tx.exec('DELETE FROM users WHERE username = $1', user.username);
sp = tx.createSavepoint();
tx.exec('INSERT INTO users VALUES ($1, $2)', user.username, user.hashedPassword);
try {
// your code that takes two minutes
tx.commit();
} catch (e) {
tx.rollbackToSavepoint(sp);
tx.commit();
}
});
}
Where you first insert your row, immediately commiting the change. Now any new user can't use that username.
Then, start a transaction, delete the user. Create a savepoint. Create the user again. Now, instead of rolling back the entire transaction if something fails, rollback to the savepoint (where the user was created then deleted, effectively a no-op). If it works, since you deleted then created again, then the delete was effectively a no-op.
I'm trying to delete an attachment from a case after the case is closed. The trigger should only fire when a custom check box on the case object is checked.
You can write an After Update trigger on the Case object, and check inside the trigger the two conditions (Case.isclosed and CustomCheck = true ), then query on the attachments whose parentid match with the id of this case. Then Database.delete the attachment list which you queried above. Hope this helps !
EDIT : Ofcourse you must bulkify your triggers as a best practice.
In trigger you should get id records who have selected check box.
When you have id's yours records run query like this:
SELECT Id ParentId FROM Attachment WHERE ParentId in: youIdList
and delete result of query.
I'm receiving an exception when I try to use data loader for this apex trigger. It says there's limit of 100 records to be updated at a time. Here is the code that explains a trigger on Account Object. All the comments are much appreciated
trigger MaintainPrimaryOverriding on Account (before insert, before update) {
if (TriggerUpdateController.getPrimaryBranchOverriding()){
TriggerAffiliationControl.setLock();
for(Account s : Trigger.new)
{
if (Trigger.isUpdate){
Id ownerId = Trigger.oldMap.get(s.Id).OwnerId;
if (s.OwnerId != ownerId){
//Use Branch Associated with owner ID
TriggerUpdateController.UpdatePrimaryBranchOfficeForAccountOwnerChange(s);
TriggerUpdateController.UpdateAffiliation(s);
}
}
else if(Trigger.isInsert){
TriggerUpdateController.UpdatePrimaryBranchOfficeForAccountOwnerChange(s);
}
}
TriggerAffiliationControl.setUnLock();
}
}
Thanks!
This error Too many SOQL queries is due to the fact, you are hitting on governor limit. Look at Best Practice: Avoid SOQL Queries Inside FOR Loops
Looks like you are calling below methods in your code for each update/insert record
"TriggerUpdateController.UpdatePrimaryBranchOfficeForAccountOwnerChange(s)"
"TriggerUpdateController.UpdateAffiliation(s)"
Looks like you are issuing SOQL statement in those above statements, which causing the governing limit error.
two modifications you have to do here.
1. Capture the list of updated or inserted accounts in a list and pass them to above two methods.
2. make above two methods to accept the list of accounts created in step1 and do the processing.
3. If you want fetch details from some other object, get those details in a SOQL.
If you could post the code for "TriggerUpdateController", i would be in a better position to suggest you correct code.
Regards,
Pundareekam Kudikala
I am referencing the 2 step newsletter example at http://agiletoolkit.org/codepad/newsletter. I modified the example into a 4 step process. The following page class is step 1, and it works to insert a new record and get the new record id. The problem is I don't want to insert this record into the database until the final step. I am not sure how to retrieve this id without using the save() function. Any ideas would be helpful.
class page_Ssp_Step1 extends Page {
function init(){
parent::init();
$p=$this;
$m=$p->add(Model_Publishers);
$form=$p->add('Form');
$form->setModel($m);
$form->addSubmit();
if($form->isSubmitted()){
$m->save();//inserts new record into db.
$new_id=$m->get('id');//gets id of new record
$this->api->memorize('new_id',$new_id);//carries id across pages
$this->js()->atk4_load($this->api->url('./Step2'))->execute();
}
}
}
There are several ways you could do this, either using atk4 functionality, mysql transactions or as a part of the design of your application.
1) Manage the id column yourself
I assume you are using an auto increment column in MySQL so one option would be to not make this auto increment but use a sequence and select the next value and save this in your memorize statement and add it in the model as a defaultValue using ->defaultValue($this->api->recall('new_id')
2) Turn off autocommit and create a transaction around the inserts
I'm from an oracle background rather than MySQL but MySQL also allows you to wrap several statements in a transaction which either saves everything or rollsback so this would also be an option if you can create a transaction, then you might still be able to save but only a complete transaction populating several tables would be committed if all steps complete.
In atk 4.1, the DBlite/mysql.php class contains some functions for transaction support but the documentation on agiletoolkit.org is incomplete and it's unclear how you change the dbConnect being used as currently you connect to a database in lib/Frontend.php using $this->dbConnect() but there is no option to pass a parameter.
It looks like you may be able to do the needed transaction commands using this at the start of the first page
$this->api->db->query('SET AUTOCOMMIT=0');
$this->api->db->query('START TRANSACTION');
then do inserts in various pages as needed. Note that everything done will be contained in a transaccion so if the user doesnt complete the process, nothing will be saved.
On the last insert,
$this->api->db->query('COMMIT');
Then if you want to, turn back on autocommit so each SQL statement is committed
$this->api->db->query('SET AUTOCOMMIT=1');
I havent tried this but hopefully that helps.
3) use beforeInsert or afterInsert
You can also look at overriding the beforeInsert function on your model which has an array of the data but I think if your id is an auto increment column, it won't have a value until the afterInsert function which has a parameter of the Id inserted.
4) use a status to indicate complete record
Finally you could use a status column on your record to indicate it is only at the first stage and this only gets updated to a complete status when the final stage is completed. Then you can have a housekeeping job that runs at intervals to remove records that didn't complete all stages. Any grid or crud where you display these records would be limited with AddCondition('status','C') in the model or added in the page so that incomplete ones never get shown.
5) Manage the transaction as non sql
As suggested by Romans, you could store the result of the form processing in session variables instead of directly into the database and then use a SQL to insert it once the last step is completed.
I would really appreciate if someone can guide me to check if a particular field is included in update call inside a before/after update trigger. Many thanks.
All fields are always present in the trigger regardless of whether they are dirty or not, to ascertain if a specific field has been modified you have to retrieve a previous version of the row using oldMap map which is a Map<ID, sObject> and compare the values in old and new. For example
trigger CaseOnParticularFieldUpdate on Case (before update) {
for (Case c: Trigger.new) {
Case oldCase = Trigger.oldMap.get(c.ID);
if (c.Field != oldCase.Field) {
// field was updated, do some magic here
}
}
}
Trigger will include all fields of that sobject for which it is invoked. You can check previous(old) value and current(new) value of any field in that object and can compare it and can do the operation accordingly.
Hope it helps you.