Are Epicor Widgets Broken? šŸ¤

going from 10.2.100 to 10.2.700, most of my predecessorā€™s Db.Validate(); calls broke. The fix was to replace with BO calls (obviously preferred) or by explicitly specifying just what was being updated with ```Db.Validate(myVarName);``

Also some BO.Update(ref blahBlah); broke until implementing the BeforeImage concept found here.

Another one has been foreach(from var thing in Db.Table blah blah select thing){ do stuff} stopped working in some, not all, cases, and I had to first do var thing = (from th in Db.Table where blah blah select th).ToList()
and then foreach the list by itself.

Iā€™m not defending widgets in their current state (actually hate 'em) but I can see that having Epicor test and vouch for them before upgrading is pretty useful, because the above examples are mostly trial-and-error.

1 Like

Whenever I need to iterate, I use a custom code block. Everywhere else I try to use widgets. Itā€™s easier for others to maintain, as not all of IT/power users are C# developers and the flow chart design is easier for them to follow.

The only other exception where I use custom code block is when a condition block destroys data. I have to use custom code blocks in order to do a simple condition test to make sure we donā€™t lose serial numbers.

I have had custom code blocks break on upgrades, so I keep track of the BPMs that have those so I can easily target those for testing on upgrades.

2 Likes

@jdewitt6029 I am sure there are some BPMs from the uplift to Epicor 9 that have a tt table joined to an ERP table.

Me (being new to Epicor) probably just converted it to C#, checked to make sure it was working, and called it a day. Not sure if this is stated in any customization guides or anything. Would be a great addition.

Same mostly. Iā€™ve had very minor things break but nothing near as bad as screen customizations. On a major version I might spend 1 hr fixing little BPMs here and there with custom code and another 9 on screens. I also use custom code blocks heavily and avoid widgets in most cases.

1 Like

Yeah, I curled up in front of the streaming TV fireplace with Orlicky and Plosslā€™s MRP books. I decided to start with Orlickyā€™s first edition and get a chronological overview of the state of the art through the decades. :crazy_face:

1 Like

Manā€¦

Amen, Jason! I would LOVE a college-type course on Epicor BPMā€™s & customization. Something where we build on weekly, have homework & test, a final project, the whole deal. I think that would close the loop for a lot of us non-computer science folks, and give enough time to learn the why, rather than rush through an example or two like Insights Extended Ed.

Having a teaching plan where each week we get a complete understanding of why something is done a certain way before building on it would be huge (at least for me). Rather than rote memorization of ā€˜do this, and it worksā€™.

Not sure if Epicor or a third-party is willing to do that, but please take my $! Iā€™m sure there are others in the same boat that fell into coding Epicor throughout their career trajectory, and didnā€™t come from a CompSci background.

Sorry for the rambleā€¦

5 Likes

Andris, way to hit the nail on the head. That is exactly what I wish we could learn about and be taught. Someone needs to start it on Coursera!

I will teach it if I can get 50 sign ups at 350$ :slight_smile: I will even buy fancy software, cameras and do a live workshop.

4 Likes

:star_struck:
:exploding_head:
:partying_face:

1 Like

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: