When you call the Update method, the business objects are supposed to compare the “original” data to the updated data to see which fields have changed (for BPMs and such). Some business objects don’t automatically add this original row to the dataset when calling Update, and so you need to manually create a new data row, use BufferCopy to copy all data to it, and then add it to the dataset before calling update.
I use this code in a function to import a data file and update jobs’ scheduled resources. It uses the same concept but for JobOpDtl. This should be the same for JobOper in your case.
BufferCopyExample
// Change resource on job if resource doesn't match method's resource. Then release the job.
CallService<Erp.Contracts.JobEntrySvcContract>(je => {
var job = jobNum;
var jobDS = je.GetByID(job);
var jobOpDtl = jobDS.JobOpDtl.FirstOrDefault(op => op.OprSeq == 10 && !op.ResourceGrpID.ToUpper().Contains("TOOL"));
// Make a copy of the row we're updating so that Epicor will actually update the records!
var newJOD = (Erp.Tablesets.JobOpDtlRow) jobDS.JobOpDtl.NewRow();
BufferCopy.Copy(jobOpDtl, newJOD);
jobDS.JobOpDtl.Add(newJOD);
jobOpDtl.RowMod = IceRow.ROWSTATE_UPDATED;
je.ChangeJobOpDtlResourceID(jobResources[job], ref jobDS);
//jobDS.JobOpDtl.FirstOrDefault(op => op.OprSeq == 10 && !op.ResourceGrpID.ToUpper().Contains("TOOL")).RowMod = IceRow.ROWSTATE_UPDATED;
je.Update(ref jobDS);
// Now release the job.
jobDS = je.GetByID(job);
var jobHead = jobDS.JobHead.FirstOrDefault();
var newJobHead = (Erp.Tablesets.JobHeadRow) jobDS.JobHead.NewRow();
BufferCopy.Copy(jobHead, newJobHead);
jobDS.JobHead.Add(newJobHead);
jobHead.JobReleased = true;
jobHead.RowMod = IceRow.ROWSTATE_UPDATED;
je.ChangeJobHeadJobReleased(ref jobDS);
je.Update(ref jobDS);
log.WriteLine($" Changed resource and released job {job}.");
});