Are Epicor Widgets Broken? 🤐

I’m in, seriously. If others are interested, can this be split off to a new thread?

1 Like

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.

2 Likes

ā€œBack when I worked at XYZ we never followed best practices, and we liked it.ā€

1 Like

I will sign up for classes. A separate thread is needed for this.

Thanks @hkeric.wci for the offer.

Vinay Kamboj

1 Like

You take Venmo? :joy::joy::joy:

Seriously though - even if it’s just a teams meeting or something I would absolutely pay $350 out of pocket for this.

1 Like

dude. uncool. I never said I’d FOLLOW them.

I wish I could give this one more likes.

2 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).
image

1 Like

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 :frowning:

2 Likes

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 :smiley:

//
// 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 :slight_smile:

1 Like