I'm trying to write a trigger where inserting, updating, or deleting a record(line item) will update an amount field. Now all this records will have same ParentID (expense) and Name(of the line items). Basically duplicate records except the Contact name will be different. So, when I add a new line item with new amount with same Parents and name, the trigger should go off and query all the line items with same Parent and should re-calculate the amount.
So, if I enter first line item and say Total amount should be 100. Then I enter 2nd line Item, trigger should fire and update Amount on both record saying '50.00'. For some reason, my trigger is not updating even though it's calculating it correctly. Where is the bug? Please help!!!
trigger Test on Expense_Line_Item__c (after insert, after update, after delete) {
set<id>testlist = new set<id>();
//List<Expense_Line_Item__c> listItem= new List<Expense_Line_Item__c>();
for (Expense_Line_Item__c a : trigger.new) {
testlist.add(a.expense__c);
}
list<Expense_Line_Item__c> mapParent =
new list<Expense_Line_Item__c>([SELECT name,
id,
Amount__c
FROM Expense_Line_Item__c
WHERE expense__c IN:testlist]);
Decimal Total = 0.0;
Integer Count = 0;
for (Expense_Line_Item__c exp : mapParent) {
Total = Total + exp.Amount__c;
Count++;
System.debug('Total during iterator::::::::::::::::::::::' + Total);
System.debug('Counter:::::::::::::::::::::::::::::::::::::' + Count);
}
if (Count > = 1)
Total = Total / Count;
System.debug('Total count after division::::::::::::::::::::::' + Total);
List <Expense_Line_Item__c> insertLineItem = new List <Expense_Line_Item__c>();
for (Expense_Line_Item__c lineItem : MapParent) {
lineItem.Amount__c = Total;
//insertLineItem.add(lineItem);
//System.debug('LineItem Amount getting inserted::::::::::::::::::::::'+lineItem.Amount__c);
}
// upsert insertLineItem;
}
It looks like your code is an After trigger. As you're in after trigger context you have to do DML to update the records.
It also seems to me you're trying to do too much in a single trigger, its always better to take each method individually so that you can fully control what you're doing.
I see you do have an Upsert DML statement commented out but you'd be better off using Update for this particular instance as you're not using an external id or making a decision to create or insert which is the purpose of upsert
Related
I'm new to salesforce and I'm trying to learn more. Currently I'm stuck at a point where I don't know what to do further. Kindly point me in the right direction. Any help is appreciated.
So what im trying to do is to compare lastnames to find duplicates when the record is being created and if a duplicate is found then instead of creating it as a new record it should be merged with existing record.
So to achieve the task I have wrote the following trigger handler:
public class LeadTriggerHandler {
public static void duplicateMerge(){
List<Lead> leadList = [SELECT Id,Name, Email, Phone, FirstName, LastName FROM Lead];
List<Lead> leadTrigger = Trigger.new;
for(Lead leadVarTrigger : leadTrigger){
for(Lead leadVar : leadList){
//System.debug(leadVar.LastName + '==' + leadVarTrigger.LastName);
if(leadVarTrigger.LastName == leadVar.LastName)
{
//System.debug(leadVar.LastName + '==' + leadVarTrigger.LastName);
//leadVarTrigger.addError('This is a duplicate record');
Database.merge(leadVar, leadVarTrigger);
System.debug('Trigger Successful');
}
}
}
}
}
the following is my trigger:
trigger LeadTrigger on Lead (after insert) {
if(Trigger.isafter && Trigger.isInsert)
{
LeadTriggerHandler.duplicateMerge();
}
}
And when I try with after insert i get the following error:
LeadTrigger: execution of AfterInsert caused by: System.DmlException: Merge failed. First exception on row 0 with id 00Q5j00000ENUGVEA5; first error: INVALID_FIELD_FOR_INSERT_UPDATE, Unable to create/update fields: Name. Please check the security settings of this field and verify that it is read/write for your profile or permission set.: [Name] Class.LeadTriggerHandler.duplicateMerge: line 18, column 1 Trigger.LeadTrigger: line 5, column 1
And if i try with before trigger i get the following error for the same code:
LeadTrigger: execution of BeforeInsert caused by: System.StringException: Invalid id at index 0: null External entry point Trigger.LeadTrigger: line 5, column 1
Actually, according to your code, you are allowing the record to be created and saved to the database by using after insert. Your before insert failed because your handler class is referencing an Id, however, if you use before logic, the record isn't saved to the database yet, meaning it doesn't have an Id. With that being said, let's try the following. :)
The Trigger (Best practice is to have one trigger with all events):
trigger TestTrigger on Lead (before insert, before update, before delete, after insert, after update, after delete, after undelete) {
if(Trigger.isafter && Trigger.isInsert)
{
//Can't conduct DML operations with trigger.new or trigger.old
//So we will create a set and send this to our handler class
Set<Id> leadIds = Trigger.newMap.keySet();
LeadTriggerHandler.duplicateMerge(leadIds);
}
}
The Handler Class:
public class LeadTriggerHandler {
public static void duplicateMerge(Set<Id> idsFromTrigger){
//Querying the database for the records created during the trigger
List<Lead> leadTrigger = [SELECT Id, LastName FROM Lead WHERE Id IN: idsFromTrigger];
List<String> lastNames = new List<String>();
//This set is important as it prevents duplicates in our dml call later on
Set<Lead> deDupedLeads = new Set<Lead>();
List<Lead> leadsToDelete = new List<Lead>();
for (Lead l : leadTrigger){
//getting all of the Last Names of the records from the trigger
lastNames.add(l.lastName);
}
//We are querying the database for records that have the same last name as
//the records that were created during our trigger
List<Lead> leadList = [SELECT Id, Name, Email, Phone, FirstName, LastName FROM Lead WHERE LastName IN: lastNames];
for(Lead leadInTrigger : leadTrigger){
for(Lead leadInList : leadList){
if(leadInTrigger.LastName == leadInList.LastName){
//if the lead from the trigger has the same last name as a lead that
//already exists, add it to our set
deDupedLeads.add(leadInTrigger);
}
}
}
//add all duplicate leads from our set to our list and delete them
leadsToDelete.addAll(deDupedLeads);
delete leadsToDelete;
}
}
This handler has been bulkified in two ways, we removed the DML operation out of the loop and the code is able to process a scenario where someone mass inserts 1000s of leads at a time. Plus, rather than querying every lead record in your database, we only query for records that have the same last name as the records created during the insert operation. We advise using something more unique than LastName like Email or Phone as many people/leads can have the same Last Name. Hope this helps and have a blessed one.
So i have written a trigger to prevent user from entering more than one opportunity product to the same opportunity, but the problem is when he adds more than one opportunity product at the same time, my trigger does not fire, salesforce takes it as one product.
What can i add to my trigger to fix this ?
My trigger :
trigger OpportunityLineItemBeforeInsert on OpportunityLineItem (before insert) {
Set<Id>opportunityIds = new Set<Id>();
// get all parent IDs
for(OpportunityLineItem i : trigger.new)
{
opportunityIds.add(i.OpportunityId);
}
// query for related Olis (Opportunity Line Items)
Map<Id, Opportunity> opps = new Map<Id, Opportunity>([SELECT ID,
(SELECT ID
FROM OpportunityLineItems)
FROM Opportunity
WHERE ID IN :opportunityIds]);
for(OpportunityLineItem i : trigger.new)
{
if(opps.get(i.OpportunityId).OpportunityLineItems.size()>0)
{
i.addError('Your Message');
}
}
}
Thank you in advance.
I would probably ignore anything related to the Oppty.
You want only one product created, so on creation, either the number of LI is 0 and you can create exactly one, or it' snot 0 and you can't create any.
I would just create a rollup field on the Oppty, count the products. If the count != 0, then fail the validation. If count = 0, then count the Olis in trigger.new and if !=1, fail.
Instead of writing code to do this you should instead create a field on products that stores the id of the parent opportunity, make that field unique, and populate the value via workflow or process builder with the id of the parent opportunity. That way if a second product gets added the unique constraint would fire and prevent the record from being inserted.
Another option would be to create a rollup on opportunity to count the number of opportunity products, then add a validation rule that show an error if the number of products > 1. The advantage of doing it this way is that you get to set the error message as opposed to the generic duplicate error message with the first option.
I am trying to create a trigger in APEX, when an custom textfield of an custom sObject gets updated with products (means, when new products get insert or existing one get deleted).
How can I compare in APEX the Trigger.Old values with the Trigger? New values of this field in order to start the Trigger.
It would look something like this:
Trigger NameOfTrigger on CustomSObject__c (after update){
/*there is already an existing list of products that get insert into the custom textfield (probably as Strings)
*/
List <String> textList = new List <String> ();
/*PseudoCode: if the textfield got updated/has changed, copy from every entry of this textfield (entry = product name as a string) and copy fieldX into another sObject
*/
if(CustomSObject.field(OldValues) != CustomSObject.field(NewValues)){
for (String product : textList){
//Trigger e.g. copy the values of a certain field of p and paste them in another sObject
}
Could somebody help me with the syntax?
You can utilize inbuilt Trigger.new and Trigger.old to get the latest and old values of any record. These lists can be used to achieve what you're looking for.
Sample example would be:
Trigger NameOfTrigger on CustomSObject__c (after update){
for(CustomSObject__c customObject : Trigger.new) {
// get old record
CustomSObject__c oldCustomObject = Trigger.oldMap.get(customObject.Id);
// compare old and new values of a particular field
if(customObject.fieldName != oldCustomObject.fieldName){
//Trigger e.g. copy the values of a certain field of p and paste them in another sObject
}
}
}
See documentation of Trigger.new & Trigger.old
When we insert multiple rows at the same time with Entity framework it's good to add these objects one by one and then commit in the end, instead of adding and comitting each time.
So in that case, how do I get IDs for those insertions?
Example:
foreach (var item in list)
{
Subscription subscription = new Subscription();
subscription.Amount = item.ItemTotal;
this.ClientRepositories.LiveData.AddToSubscriptions(subscription);
// LiveData is db context via webservice proxy
// how to get IDS of these insertions if you do not commit each time?
int id = subscription.Id;
someOtherOperation(id); //i need to insert ID for each row that was inserted
}
this.ClientRepositories.LiveData.SaveChanges();
If I use commit inside for each time it adds new object I can easily get the latest inserted ID, but its not good to commit each time for multiple rows I heard.
I'm assuming that you are using auto incremented id's. I think your going to have to keep a list of your subscription objects outside of your loop. During each loop iteration, add the current subscription to the list. After your execute 'SaveChanges();' The subscription id's will be automatically added.
Would look like this:
List<Subscription> subList = new List<Subscription>();
foreach (var item in list)
{
Subscription subscription = new Subscription();
subscription.Amount = item.ItemTotal;
this.ClientRepositories.LiveData.AddToSubscriptions(subscription);
subList.add(subscription);
}
this.ClientRepositories.LiveData.SaveChanges();
/* At this point, each Subscription in your 'subList' should have an id */
Using LINQ-to-Entities 4.0, is there a correct pattern or construct for safely implementing "if not exists then insert"?
For example, I currently have a table that tracks "user favorites" - users can add or remove articles from their list of favorites.
The underlying table is not a true many-to-many relationship, but instead tracks some additional information such as the date the favorite was added.
CREATE TABLE UserFavorite
(
FavoriteId int not null identity(1,1) primary key,
UserId int not null,
ArticleId int not null
);
CREATE UNIQUE INDEX IX_UserFavorite_1 ON UserFavorite (UserId, ArticleId);
Inserting two favorites with the same User/Article pair results in a duplicate key error, as desired.
I've currently implemented the "if not exists then insert" logic in the data layer using C#:
if (!entities.FavoriteArticles.Any(
f => f.UserId == userId &&
f.ArticleId == articleId))
{
FavoriteArticle favorite = new FavoriteArticle();
favorite.UserId = userId;
favorite.ArticleId = articleId;
favorite.DateAdded = DateTime.Now;
Entities.AddToFavoriteArticles(favorite);
Entities.SaveChanges();
}
The problem with this implementation is that it's susceptible to race conditions. For example, if a user double-clicks the "add to favorites" link two requests could be sent to the server. The first request succeeds, while the second request (the one the user sees) fails with an UpdateException wrapping a SqlException for the duplicate key error.
With T-SQL stored procedures I can use transactions with lock hints to ensure a race condition never occurs. Is there a clean method for avoiding the race condition in Entity Framework without resorting to stored procedures or blindly swallowing exceptions?
You can also write a stored procedure that uses some new tricks from sql 2005+
Use your combined unique ID (userID + articleID) in an update statement, then use the ##RowCount function to see if the row count > 0 if it's 1 (or more), the update has found a row matching your userID and ArticleID, if it's 0, then you're all clear to insert.
e.g.
Update tablex set userID = #UserID, ArticleID = #ArticleID (you could have more properties here, as long as the where holds a combined unique ID) where userID = #UserID and ArticleID = #ArticleID
if (##RowCount = 0)
Begin
Insert Into tablex ...
End
Best of all, it's all done in one call, so you don't have to first compare the data and then determine if you should insert. And of course it will stop any dulplicate inserts and won't throw any errors (gracefully?)
You could try to wrap it in a transaction combined with the 'famous' try/catch pattern:
using (var scope = new TransactionScope())
try
{
//...do your thing...
scope.Complete();
}
catch (UpdateException ex)
{
// here the second request ends up...
}