please help me in converting my after trigger to batch apex.
This trigger fires when opportunity stage changes to won.
It runs through line items and checks if forecast(custom objet) exists with that acunt.if yes,iit links to them..if no,itt will create a new forecat.
my trigger works fine forr some records.but to mass update i am getting timed out error.So opting batch apex but i had never written it.pls help me.
trigger Accountforecast on Opportunity (after insert,after update) {
List<Acc_c> AccproductList =new List<Acc_c>();
List<Opportunitylineitem> opplinitemlist =new List<Opportunitylineitem>();
list<opportunitylineitem > oppdate= new list<opportunitylineitem >();
List<Acc__c> accquery =new List<Acc__c>();
List<date> dt =new List<date>();
Set<Id> sProductIds = new Set<Id>();
Set<Id> sAccountIds = new Set<Id>();
Set<id> saccprodfcstids =new set<Id>();
Acc__c accpro =new Acc__c();
string aname;
Integer i;
Integer myIntMonth;
Integer myIntyear;
Integer myIntdate;
opplinitemlist=[select Id,PricebookEntry.Product2.Name,opp_account__c,Opp_account_name__c,PricebookEntry.Product2.id, quantity,ServiceDate,Acc_Product_Fcst__c from Opportunitylineitem WHERE Opportunityid IN :Trigger.newMap.keySet() AND Acc__c=''];
for(OpportunityLineItem oli:opplinitemlist) {
sProductIds.add(oli.PricebookEntry.Product2.id);
sAccountIds.add(oli.opp_account__c);
}
accquery=[select id,Total_Qty_Ordered__c,Last_Order_Qty__c,Last_Order_Date__c,Fcst_Days_Period__c from Acc__c where Acc__c.product__c In :sproductids and Acc__c.Account__c in :saccountids];
for(Acc__c apf1 :accquery){
saccprodfcstids.add(apf1.id);
}
if(saccprodfcstids!=null){
oppdate=[select servicedate from opportunitylineitem where Acc__c IN :saccprodfcstids ];
i =[select count() from Opportunitylineitem where acc_product_fcst__c in :saccprodfcstids];
}
for(Opportunity opp :trigger.new)
{
if(opp.Stagename=='Closed Won')
{
for(opportunitylineitem opplist:opplinitemlist)
{
if(!accquery.isempty())
{
for(opportunitylineitem opldt :oppdate)
{
string myDate = String.valueOf(opldt);
myDate = myDate.substring(myDate.indexof('ServiceDate=')+12);
myDate = myDate.substring(0,10);
String[] strDate = myDate.split('-');
myIntMonth = integer.valueOf(strDate[1]);
myIntYear = integer.valueOf(strDate[0]);
myIntDate = integer.valueOf(strDate[2]);
Date d = Date.newInstance(myIntYear, myIntMonth, myIntDate);
dt.add(d);
}
dt.add(opp.closedate);
dt.sort();
integer TDays=0;
system.debug('*************dt:'+dt.size());
for(integer c=0;c<dt.size()-1;c++)
{
TDays=TDays+dt[c].daysBetween(dt[c+1]);
}
for(Acc_product_fcst__c apf:accquery)
{
apf.Fcst_Days_Period__c = TDays/i;
apf.Total_Qty_Ordered__c =apf.Total_Qty_Ordered__c +opplist.quantity;
apf.Last_Order_Qty__c=opplist.quantity;
apf.Last_Order_Date__c=opp.CloseDate ;
apf.Fcst_Qty_Avg__c=apf.Total_Qty_Ordered__c/(i+1);
Opplist.Acc__c =apf.Id;
}
}
else{
accpro.Account__c=opplist.opp_account__c;
accpro.product__c=opplist.PricebookEntry.Product2.Id;
accpro.opplineitemid__c=opplist.id;
accpro.Total_Qty_Ordered__c =opplist.quantity;
accpro.Last_Order_Qty__c=opplist.quantity;
accpro.Last_Order_Date__c=opp.CloseDate;
accpro.Fcst_Qty_Avg__c=opplist.quantity;
accpro.Fcst_Days_Period__c=7;
accproductList.add(accpro);
}
}
}
}
if(!accproductlist.isempty()){
insert accproductlist;
}
update opplinitemlist;
update accquery;
}
First of all, you should take a look at this: Apex Batch Processing
Once you get a better idea on how batches work, we need to take into account the following points:
Identify the object that requires more processing. Account? Opportunity?
Should the data be maintained across batch calls? Stateful?
Use correct data structure in terms of performance. Map, List?
From your code, we can see you have three objects: OpportunityLineItems, Accounts, and Opportunities. It seems that your account object is using the most processing here.
It seems you're just keeping track of dates and not doing any aggregations. Thus, you don't need to maintain state across batch calls.
Your code has a potential of hitting governor limits, especially memory limits on the heap. You have a four-nested loop. Our suggestion would be to maintain opportunity line items related to Opportunities in a Map rather than in a List. Plus, we can get rid of those unnecessary for loops by refactoring the code as follows:
Note: This is just a template for the batch you will need to construct.
globalglobal Database.QueryLocator start(Database.BatchableContext BC) class AccountforecastBatch implements Database.Batchable<sObject>
{
global Database.QueryLocator start(Database.BatchableContext BC)
{
// 1. Do some initialization here: (i.e. for(OpportunityLineItem oli:opplinitemlist) {sProductIds.add(oli.PricebookEntry.Product2.id)..}
// 2. return Opportunity object here: return Database.getQueryLocator([select id,Total_Qty_Ordered__c,Last_Order_Qty ....]);
}
global void execute(Database.BatchableContext BC, List<sObject> scope)
{
// 1. Traverse your scope which at this point will be a list of Accounts
// 2. You're adding dates inside the process for Opportunity Line Items. See if you can isolate this process outside the for loops with a Map data structure.
// 3. You have 3 potential database transactions here (insert accproductlist;update opplinitemlist; update accquery; ). Ideally, you will only need one DB transaction per batch.If you can complete step 2 above, you might only need to update your opportunity line items. Otherwise, you're trying to do more than one thing in a method and you will need to redesign your solution
}
global void finish(Database.BatchableContext BC)
{
// send email or do some other tasks here
}
}
Related
This is my Apex Trigger in which I am taking data from Custom Field "Picklist Multiple" named(Opportunity_Picklist__c) in Opportunity and automatically paste it to a custom field "text" in Account. Now every selected option from Custom Field "Picklist Multiple" is coming to the custom field "text". I want to copy only Custom Field "Picklist Multiple" values with API Name "Premium" and "Basic". Other selected values can be avoided. Please look into the code below and suggest me relatable changes.
Custom Field "Picklist Multiple" have these data:-
Premium
Advanced
Basic
Free
If someone selected all four values in Custom Field "Picklist Multiple" they can remain as it is but I don't want all of them to be copied in custom field "text". I want the values with API Names "Premium" and "Basic" only in the custom field "text".
trigger OppTrigger on Opportunity (After insert,After update,After delete) {
if((Trigger.isInsert|| Trigger.isupdate|| Trigger.isDelete) && Trigger.isAfter)
{
Set<Id> acctSet = new Set<Id>();
List<Account> accList = new List<Account>();
for(Opportunity opp : Trigger.new)
{
acctSet.add(opp.AccountId);
}
Map<Id, Account> mapAccounts = new Map<Id, Account>([SELECT Id, Plan__c FROM Account where Id IN :acctSet]);
for(Opportunity opp : Trigger.new)
{
Account acc = mapAccounts.get(opp.AccountId);
If(Trigger.IsDelete){
acc.Plan__c = null;
}
if(Trigger.isInsert|| Trigger.isupdate){
acc.Plan__c = opp.Opportunity_Picklist__c;
}
accList.add(acc);
}
Update accList;
}
}
You'll be better off cutting it up a bit. Use a helper class as trigger handler. Will let you create some reusable functions rather than just a giant block of code executing top-down. There's this for general reading, I like that one but there are lots of examples, what you'll be comfortable with.
For example imagine a method called static void rollupToAccounts(Set<Id> ids). It'd accept account ids and you could call it from different scenarios (because you'll have different entry points. Insert is easy, base it on trigger.new, cool. Delete is funnier because there's no trigger.new, you need to get Account Ids out of trigger.old And after update if I move opportunity from Account A to B - your code should recalculate both! Good luck writing straight in the trigger's body.
As for actual "meat" of the method... many ways to do it, something like this?
static void rollupToAccounts(Set<Id> ids){
final Set<String> valuesToRollup = new Set<String>{'Premium', 'Basic'};
List<Account> toUpdate = new List<Account>();
List<Account> accounts = [SELECT Id, Plan__c,
(SELECT Opportunity_Picklist__c
FROM Opportunities
WHERE Opportunity_Picklist__c INCLUDES :valuesToRollup)
FROM Account WHERE Id IN :ids];
for(Account a : accounts){
Set<String> currentValues new Set<String>();
if(String.isNotBlank(a.Plan__c)){
currentValues.addAll(a.Plan__c.split(';'));
}
Set<String> newValues = new Set<String>();
if(!a.Opportunities.isEmpty()){
for(Opportunity o : a.Opportunities){
newValues.addAll(o.Opportunity_Picklist__c.split(';')); // this will include Advanced & Free too, it's ok, we'll fix it
}
newValues.retainAll(valuesToRollup); // filter out all the ones we don't want
}
System.debug('old: ' + currentValues + '; new: ' + newValues);
if(currentValues != newValues){
// we'll need to update this account with current data
List<String> temp = new List<String>(currentValues);
temp.sort();
a.Plan__c = String.join(temp, ';');
toUpdate.add(a);
}
}
System.debug(toUpdate);
update toUpdate;
}
It's not perfect. If you have 100 opportunities for same account with "Basic" it'll take them all and possibly waste query rows. You could do it as 2 (sub)queries, WHERE Opportunity_Picklist__c INCLUDES ('Basic') LIMIT 1 and then another query for Premium if you think it'd be a problem..
Why not just check the value of the opportunity picklist before saving it into the account value? You could write this code a little cleaner and more efficient. However, a simple solution is just to check if the values you want exist.
if(Trigger.isInsert|| Trigger.isupdate){
String strAccountPlan;
if(opp.Opportunity__PickList__c.Contains('Premium'){
strAccountPlan = 'Premium';
}
if(opp.Opportunity__PickList__c.Containt('Basic'){
if(String.isBlank(strAccountPlan){
strAccountPlan = 'Basic');
}
else{
strAccountPlan += ';Basic';
}
acc.Plan__c = strAccountPlan;
}
I have try to clone Opportunity records and done till 3 fix field But I need for all fields.But without using clone() method.
Code -
public class Cloningwithout {
public static void insertclone(){
Opportunity opp = new Opportunity(Name='Opportunity2', CloseDate=date.parse('06/07/2012'),StageName='Prospecting');
insert opp;
ID Oppid =opp.ID;
clone1(Oppid);
}
public static void clone1(String IDs){
Opportunity sourceopp = [SELECT Id,Name,CloseDate,StageName from Opportunity Where Id=:IDs];
Opportunity targertopp = new Opportunity();
targertopp.Name = sourceopp.Name;
targertopp.CloseDate = sourceopp.CloseDate;
targertopp.StageName = sourceopp.StageName;
insert targertopp;
} }
maximum trigger depth exceeded
Your trigger runs after Opportunity is inserted. It inserts new opportunity, which causes the trigger to run, which inserts new opportunity... Until about 16 recursive operations, then SF terminates your action. Using clone() or not has nothing to do with it.
What are you trying to do, insert just once and then skip the trigger? You could have helper checkbox on Opportunity or a static variable
trigger CloneTrigger on Opportunity (after insert) {
static Boolean skip = false;
if(!skip){
skip = true;
Opportunity[] ls=new Opportunity[]{};
for(Opportunity o: trigger.new){
Opportunity obj=new Opportunity();
// obj.id = null;
obj.name=o.name;
obj.CloseDate = o.CloseDate;
obj.StageName = o.StageName;
ls.add(obj);
}
insert ls;
}
}
I just wrote this trigger and it seems to be working fine in dev, I need to move it into production, however, the test class I wrote passes the test but does not cover the trigger. Any help would be greatly appreciated. I am a bit green here. I know I should be inserting a contact (the account is a req field) then updating the contact field I just have no earthly clue how to do taht. Thank you
trigger PropOwned on Contact (after update) {
for (Contact c : Trigger.new) {
McLabs2__Ownership__c ownNew = new McLabs2__Ownership__c();
Contact oldContact = Trigger.oldMap.get(c.id);
if (c.One_Prop_Owned__c != oldContact.One_Prop_Owned__c && c.One_Prop_Owned__c != null) {
ownNew.McLabs2__Contact__c = c.id;
ownNew.McLabs2__Property__c = c.One_Prop_Owned__c;
insert ownNew;
}
}
}
This is the test class I wrote.
#isTest
public class TestOwnership {
static testMethod void createOwnership() {
McLabs2__Ownership__c ownNew = new McLabs2__Ownership__c();
ownNew.McLabs2__Contact__c = 'Michael Webb';
ownNew.McLabs2__Property__c = '131 West 33rd Street';
insert ownNew;
}
}
Your test class just creates a McLabs2__Ownership__c object and inserts this object in database. As a result of this trigger on McLabs2__Ownership__c(if exist) will be invoked, but you have to test a trigger on Contact object. Thus you need to insert an account and after that update it because your contact trigger works in after update mode.
So, you need something like that
#isTest
private class TestOwnership {
static testMethod void whenContactUpdatedNewOwnershipIsInserted() {
// create contact, you have to replace 'value1' with appropriate data type
Contact contact = new Contact(name = 'Test Contact', One_Prop_Owned__c = 'value1');
insert contact;
contact.One_Prop_Owned__c = 'value2'; // you have to replace value2 with appropriate data type
update contact;
// in this place you should has only one record of McLabs2__Ownership__c in database, because in test context real data isn't visible
List<McLabs2__Ownership__c> ownerships = [SELECT Id, McLabs2__Contact__c, McLabs2__Property__c FROM McLabs2__Ownership__c];
System.assertEquals(1, ownerships.size());
System.assertEquals(contact.Id, ownerships[0].McLabs2__Contact__c);
System.assertEquals(contact.One_Prop_Owned__c, ownerships[0].McLabs2__Property__c);
}
}
Read the following articles which might be pretty useful for you:
Apex Trigger best practice
SOQL in loop
I am trying to write a trigger that will update a RTA field on a parent object with an image from a RTA field on the child object.
I am getting the below error when creating the child object
Apex trigger Updateparent caused an unexpected exception, contact your administrator: Updateparent: execution of AfterInsert caused by: System.StringException: Invalid id: (.....IMAGE IS DISPLAYED HERE....)External entry point.
Info
Man Utd is the child object
Account is the parent object
Image__c is the RTA field on the child object
Copy_image__c is the RTA field on accounts
Here is the trigger code
trigger Updateparent on Man_Utd_1__c (after insert, after update) {
Map<ID, Account> parentAccounts = new Map<ID, Account>();
List<Id> listIds = new List<Id>();
for (Man_Utd_1__c childObj : Trigger.new) {
listIds.add(childObj.Image__c);
}
parentAccounts = new Map<Id, Account>([SELECT id, (SELECT ID, Image__c FROM Man_Utd_s__r) FROM Account WHERE ID IN :listIds]);
for (Man_Utd_1__c manu : Trigger.new) {
Account myParentAccounts = parentAccounts.get(manu.Image__c);
myParentAccounts.Copy_image__c = manu.Image__c;
}
update parentAccounts.values();
}
Can anyone advise on how to rectify this or if it is even possible to do?
EDIT to answer comments
This one works for me, there's a slight modification around the null check. Give it a go? The field is called Image__c both on Account and Contact.
trigger rollupImage on Contact (after insert, after update) {
Set<Account> parents = new Set<Account>();
for (Contact child : trigger.new) {
if(child.AccountId != null && child.Image__c != null){
parents.add(new Account(Id = child.AccountId, Image__c = child.Image__c));
}
}
if(!parents.isEmpty()){
List<Account> toUpdate = new List<Account>();
toUpdate.addAll(parents);
update toUpdate;
}
}
ORIGINAL
The error
It's all written there ;)
List<Id> listIds = new List<Id>();
for (Man_Utd_1__c childObj : Trigger.new) {
listIds.add(childObj.Image__c); // boom happens here?
}
You're assigning rich text area value (very long string with encoded image in it) to Id variable (15 or 18 char special string)
Try like this:
Set<Id> ids = new Set<Id>(); // Set will ensure we won't have duplicate values
for (Man_Utd_1__c childObj : Trigger.new) {
ids.add(childObj.Account__c);
}
Optimization - round 1
In the second loop you're setting the Copy_Image__c field but on the local copy of Account (local variable in the scope of the loop). I'm not sure setting it there will propagate the change to the parent acc (it might be a full copy and not a reference to the item in the map. You could put it back (parentAccounts.put(manu.Account__c, myParentAccounts)) to the map or just do the image assignment directly:
parentAccounts = new Map<Id, Account>([SELECT id FROM Account WHERE ID IN :ids]);
// I've removed the subquery, you don't need it, right?
for (Man_Utd_1__c manu : Trigger.new) {
if(parentAccounts.containsKey(manu.Account__c)){
parentAccounts.get(manu.Account__c).Copy_image__c = manu.Image__c;
}
}
update parentAccounts.values();
Optimization - round 2
It looks bit stupid - we query for Accounts but all we need to fetch is the Id... But we know the Id already, right? So - behold this "pro trick" (shared just because my colleague is a big fan of Man U ;))
trigger Updateparent on Man_Utd_1__c (after insert, after update) {
Set<Account> parents = new Set<Account>();
for (Man_Utd_1__c child : trigger.new) {
if(child.Account__c != null){
parents.add(new Account(Id = child.Account__c, Copy_Image__c = child.Image__c));
}
}
if(!parents.isEmpty()){
List<Account> toUpdate = new List<Account>();
toUpdate.addAll(parents);
update toUpdate;
}
}
Note: Optimization is the key thing while writing trigger.
While writing trigger make sure you follow all the best practises:
Keynotes On Trigger:
OPTIMIZATION - I have specified it in Uppercase and in first line because its the key thing while writing logic
Proper indentation and code commenting, so that anyone who works on the same code will easily understand the logic by just glancing on the comments. Make sure you update lastModifiedBy line on initial comments so that it will be helpful to keep a trace who worked last/updated last and on what date. Better keep proper code comments on every line- IF NEEDED ONLY
Variables with proper names ex: if var of list type then 'lst' prefix or 'List' postfix and then the proper name ex: lstContactsToInsert OR lstContactsToUpdate
Always create a seperate handler class and don't write all the logics on trigger itself 'AS A BEST PRACTISE TO FOLLOW'
Have proper trigger contexts. Know when to use proper trigger contexts through salesforce documentations on trigger. Use 'Before' contexts if it can be handled using this, If Not then go for 'After' contexts so you limit DML's.
Keynotes On Handler:
The First 3 steps as mentioned for trigger applies here as well
Use common private methods where you write logic and use it in other methods
Keynotes On TestClass:
Always write a test class utilizing 'GENERIC TEST DATA CLASS' in your test class.
Use proper test method names like: test_Method_One 'OR' testImageUpdates as such
Use asserts and test all usecases - Both negative and positive tests
#1. Trigger
/**
* #TriggerName : ContactTrigger
* #CreatedBy : Rajesh Kamath
* #CreatedOn : 30-Nov, 2016
* #Description : Trigger to update the image on parent object 'Account' based on the child object 'Contact'
* #LastModified: None
*/
trigger ContactTrigger on Contact(after insert, after update) {
//Instantiate the handler class
ContactTriggerHandler objContactHandler = new ContactTriggerHandler();
/* Trigger Context */
if(Trigger.isAfter) {
//Fire on Insert
if(Trigger.isInsert) {
objContactHandler.onAfterInsert(trigger.new);
}
//Fire on Update
if(Trigger.isUpdate) {
objContactHandler.onAfterUpdate(trigger.new, trigger.oldMap);
}
}
}
#2. Handler
/**
* #HandlerName : ContactTriggerHandler
* #TriggerName : ContactTrigger
* #TestClassName : ContactTriggerHandlerTest [Make sure you create this with the steps at the end of this handler]
* #CreatedBy : Rajesh Kamath
* #CreatedOn : 30-Nov, 2016
* #Description : Trigger to update the image on parent object 'Account' based on the child object 'Contact'
* #LastModified: None
*/
public with sharing class ContactTriggerHandler {
/*
#MethodName : onAfterInsert
#Parameters : List<Contact> lstNewContactRecords
#LastModifiedBy : None [Make sure when anyone updates even this line will be updated for help for other users who work on later phase]
*/
public void onAfterInsert(List<Contact> lstNewContactRecords){
//Call a common method where we have logic
reflectChildImageOnParent(lstNewContactRecords, null);
}
/*
#MethodName : onAfterUpdate
#Parameters : List<Contact> lstContactRecords, Map<Id, Contact> mapOldContactRecords
#LastModifiedBy : None [Make sure when anyone updates even this line will be updated for help for other users who work on later phase]
*/
public void onAfterUpdate(List<Contact> lstContactRecords, Map<Id, Contact> mapOldContactRecords){
//Call a common method where we have logic
reflectChildImageOnParent(lstContactRecords, mapOldContactRecords);
}
/*
#MethodName : reflectChildImageOnParent
#Parameters : List<Contact> lstContactRecords, Map<Id, Contact> mapOldContactRecords
#LastModifiedBy : None [Make sure when anyone updates even this line will be updated for help for other users who work on later phase]
*/
private static void reflectChildImageOnParent(List<Contact> lstContactRecords, Map<Id, Contact> mapOldContactRecords){
/* Local variable declaration */
List<Account> lstAccountsToUpdate = new List<Account>();
for(Contact objContact : lstContactRecords) {
//Execute on insert and update
if((Trigger.isInsert || (Trigger.isUpdate && objContact.Child_Image__c != mapOldContactRecords.get(objContact.Id).Child_Image__c)) && objContact.Account__c != null) {
//List of accounts to update
lstAccountsToUpdate.add(new Account(Id = objContact.Account__c, Parent_Image__c = objContact.Child_Image__c));
}
}
//Update the account records
if(!lstAccountsToUpdate.isEmpty()) {
update lstAccountsToUpdate;
}
}
}
This can be referred for your usecase.
Please mark as 'Best Answer' if this was helpful
loyalty - Child Obj
fields:
lightingdata__Contact__c (relationship)
lightingdata__imagenims__c // Image field
Contact - Parent Obj
field: lightingdata__Imagefromloyalty__c
trigger loyaltytocon on Loyalty__c (after insert, after update) {
Map<id, Contact> conlist;
List<Id> idlist=new List<id>();
for (Loyalty__c loy:trigger.new) {
idlist.add(loy.lightingdata__Contact__c);
}
conlist= new Map<id, contact>([select id, lightingdata__Imagefromloyalty__c from contact where id In : idlist]);
for(Loyalty__c lo:trigger.new){
contact con = conlist.get(lo.lightingdata__Contact__c) ;
system.debug('con data' + con);
con.lightingdata__Imagefromloyalty__c = lo.lightingdata__imagenims__c;
}
update conlist.values();
}
In pareant object field it will return that image filed have URL.
Then you can create one formula filed and just refer (lightingdata__Imagefromloyalty__c) in formul it will display the image.
I'm currently inserting/updating fields like this (if there's a better way, please say so - we're always learning)
public void UpdateChallengeAnswers(List<ChallengeAnswerInfo> model, Decimal field_id, Decimal loggedUserId)
{
JK_ChallengeAnswers o;
foreach (ChallengeAnswerInfo a in model)
{
o = this.FindChallengeAnswerById(a.ChallengeAnswerId);
if (o == null) o = new JK_ChallengeAnswers();
o.answer = FilterString(a.Answer);
o.correct = a.Correct;
o.link_text = "";
o.link_url = "";
o.position = FilterInt(a.Position);
o.updated_user = loggedUserId;
o.updated_date = DateTime.UtcNow;
if (o.challenge_id == 0)
{
// New record
o.challenge_id = field_id; // FK
o.created_user = loggedUserId;
o.created_date = DateTime.UtcNow;
db.JK_ChallengeAnswers.AddObject(o);
}
else
{
// Update record
this.Save();
}
}
this.Save(); // Commit changes
}
As you can see there is 2 times this.Save() (witch invokes db.SaveChanges();)
when Adding we place the new object into a Place Holder with the AddObject method, in other words, the new object is not committed right away and we can place as many objects we want.
But when it's an update, I need to Save first before moving on to the next object, is there a method that I can use in order to, let's say:
if (o.challenge_id == 0)
{
// New record
o.challenge_id = field_id;
o.created_user = loggedUserId;
o.created_date = DateTime.UtcNow;
db.JK_ChallengeAnswers.AddObject(o);
}
else
{
// Update record
db.JK_ChallengeAnswers.RetainObject(o);
}
}
this.Save(); // Only save once when all objects are ready to commit
}
So if there are 5 updates, I don't need to save into the database 5 times, but only once at the end.
Thank you.
Well if you have an object which is attached to the graph, if you modify values of this object, then the entity is marked as Modified.
If you simply do .AddObject, then the entity is marked as Added.
Nothing has happened yet - only staging of the graph.
Then, when you execute SaveChanges(), EF will translate the entries in the OSM to relevant store queries.
Your code looks a bit strange. Have you debugged through (and ran a SQL trace) to see what is actually getting executed? Because i can't see why you need that first .Save, because inline with my above points, since your modifying the entities in the first few lines of the method, an UPDATE statement will most likely always get executed, regardless of the ID.
I suggest you refactor your code to handle new/modified in seperate method. (ideally via a Repository)
Taken from Employee Info Starter Kit, you can consider the code snippet as below:
public void UpdateEmployee(Employee updatedEmployee)
{
//attaching and making ready for parsistance
if (updatedEmployee.EntityState == EntityState.Detached)
_DatabaseContext.Employees.Attach(updatedEmployee);
_DatabaseContext.ObjectStateManager.ChangeObjectState(updatedEmployee, System.Data.EntityState.Modified);
_DatabaseContext.SaveChanges();
}