Iām in, seriously. If others are interested, can this be split off to a new thread?
I will say the words ābest practiceā āthis is what was missingā and āscalableā and I think our company would sign up at least a couple of people. Even if they donāt, Iām in.
āBack when I worked at XYZ we never followed best practices, and we liked it.ā
I will sign up for classes. A separate thread is needed for this.
Thanks @hkeric.wci for the offer.
Vinay Kamboj
You take Venmo? 


Seriously though - even if itās just a teams meeting or something I would absolutely pay $350 out of pocket for this.
dude. uncool. I never said Iād FOLLOW them.
I wish I could give this one more likes.
@SAD just wanted to throw this out for reference.
If you use Client WCF it actually handles the BeforeImage and stripping everything out from the dataset.
If you invoke a BO from a BPM it doesnt handle BeforeImage and it also sends the entire dataset to your PRE BPM:
using (var bo = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.ReceiptSvcContract>(this.Db))
{
var dsReceipt = bo.GetByID(vendorNum, purPoint, packSlip);
int seqNum = GetReceiptLCMiscChargeSeq(Session.CompanyID, vendorNum, purPoint, packSlip);
var row = dsReceipt.RcvMisc.Where(x => x.MiscSeq == seqNum).FirstOrDefault();
row.RowMod = IceRow.ROWSTATE_UPDATED;
bo.OnChangeMiscDocActualAmt(vendorNum, purPoint, packSlip, seqNum, 10.00, ref dsReceipt);
bo.Update(ref dsReceipt);
}
Which makes calling a Business Object from a BO sometimes tricky. Nontheless. I wonder if Epicor Functions can use the BO.Proxy or if someone who calls multiple BOās in a eFX needs to really, really know what theyre doing (havent tried yet).

The BPM and EFx behave like any other server-side code. So, as in the case of other areas, it is the ādeveloperāsā responsibility to call a service method correctly.
Moreover, (it seems to me that Iāve mentioned that earlier), even client code creates diff-grams only when tableset parameter has ref modifier. If a parameter is āby valueā (no modifier provided), then a client proxy sends the whole dataset to the server.
Potentially, Invoke BO method action can be updated to behave like a client-side proxy, but it is the breaking change. I see only one solution that can be applied - a special check-box in Invoke BO Method action configuration (something like āmimicks smart client proxyā).
Actually, Iām mostly sure that this behavior was implemented as the performance optimization - to minimize data traffic between client and server. In the case of the server-only code, such behavior creates unneeded memory allocations 
Gotcha. I should be fine. In cases when I need a more Client like approach when my Facades (BPMs) are legacy and not well written (should be fixed). I use something along the lines of:
This way I get Unmodified Row and Modified Row as well as required parent rows. But in most cases It may not matter⦠Well unless you are doing an Order dataset with 100 lines and 500 releases 
//
// For show only Code should be optimized
//
var dsReceipt = bo.GetByID(vendorNum, purPoint, packSlip);
// Probably another good way to fill it instead is using GetRows
//
// Example how to Copy a few Tableset rows to New Tableset
//
var dsNew = new Erp.Tablesets.ReceiptTableset {
RcvHead = {
Epicor.Data.BufferCopy.Clone(dsReceipt.RcvHead[0])
},
RcvMisc = {
Epicor.Data.BufferCopy.Clone(dsReceipt.RcvMisc.Where(x => x.MiscSeq == seqNum).FirstOrDefault()),
Epicor.Data.BufferCopy.Clone(dsReceipt.RcvMisc.Where(x => x.MiscSeq == seqNum).FirstOrDefault())
//new Erp.Tablesets.RcvMiscRow { EnableToBuildYourOwn }
}
};
dsNew.RcvMisc[0].RowMod = "U";
//
// Example how to Copy a LINQ Row to Tableset
//
// Erp.Tablesets.ReceiptTableset
var dsNew3 = new Erp.Tablesets.ReceiptTableset {
RcvHead = {
Epicor.Data.BufferCopy.Copy<Erp.Tables.RcvHead, Erp.Tablesets.RcvHeadRow>(Db.RcvHead.Where(s => s.Company == Session.CompanyID
&& s.PackSlip == packSlip
&& s.VendorNum == vendorNum
&& s.PurPoint == purPoint).FirstOrDefault())
},
// Erp.Tablesets.RcvMiscTable
RcvMisc = {
// Erp.Tablesets.RcvMiscRow
Epicor.Data.BufferCopy.Copy<Erp.Tables.RcvMisc, Erp.Tablesets.RcvMiscRow>(Db.RcvMisc.Where(s => s.Company == Session.CompanyID
&& s.PackSlip == packSlip
&& s.VendorNum == vendorNum
&& s.PurPoint == purPoint
&& s.MiscSeq == seqNum).FirstOrDefault()),
// Erp.Tablesets.RcvMiscRow
Epicor.Data.BufferCopy.Copy<Erp.Tables.RcvMisc, Erp.Tablesets.RcvMiscRow>(Db.RcvMisc.Where(s => s.Company == Session.CompanyID
&& s.PackSlip == packSlip
&& s.VendorNum == vendorNum
&& s.PurPoint == purPoint
&& s.MiscSeq == seqNum).FirstOrDefault())
}
};
dsNew3.RcvMisc[0].RowMod = "U";
bo.OnChangeMiscDocActualAmt(vendorNum, purPoint, packSlip, seqNum, _value, ref dsNew);
bo.Update(ref dsNew);
//
// Example how to Copy a LINQ Row to a LINQ Row (Erp.Tables.RcvMisc)
//
// Epicor Clone and Copy both will create a new SysRowID when the target is a LINQ Row
var LINQtoLINQExample = Epicor.Data.BufferCopy.Clone(Db.RcvMisc.Where(s => s.Company == Session.CompanyID
&& s.PackSlip == packSlip
&& s.VendorNum == vendorNum
&& s.PurPoint == purPoint
&& s.MiscSeq == seqNum).FirstOrDefault());
// If you want to Preserve the GUID you must use Copy with 3rd param true
Erp.Tables.RcvMisc linqRowExample2 = new Erp.Tables.RcvMisc();
Epicor.Data.BufferCopy.Copy(xrow, linqRowExample2, true);
I have yet to figure out the RowMod āDā sometimes RowMod D doesnt work and you have to use SetRowState but then the Facade doesnt always get notified, because you basically deleted the row from your dataset.
// ILSpy how Epicor Uan UpdateExt handle RowMod D
// They Delete the original row but keep a 2nd version with RowMod "D"
IceRow firstRow = (IceRow)fullTs.Tables[0][0];
IceRow backupRow2 = CreateNewRow(workingTs.Tables[0]);
OnUpdateExtCopyRowForTable(UpdateExtRowAction.BackupExistingRow, firstRow, backupRow2, updateSystemColumns: true);
workingTs.Tables[0].Add(backupRow2);
backupRow2.SetRowState(IceRowState.Unchanged);
firstRow.RowMod = "D";
IceRow deletedRow = CreateNewRow(workingTs.Tables[0]);
OnUpdateExtCopyRowForTable(UpdateExtRowAction.BackupExistingRow, firstRow, deletedRow, updateSystemColumns: true);
workingTs.Tables[0].Add(deletedRow);
deletedRow.SetRowState(IceRowState.Deleted);
changedRow = deletedRow;
hasDif = true;
Perhaps I should use UpdateExt, it might be fine in this scenario and may not cause bugs because it looks like it may even be handling the Before/After Rows 

