I am working on a method directive on CustShip.UpdateMaster that will create a CMI PO and then receive the PO.
Everything was going great until I started the work on the receipt. I added the Receipt contract and when it compiles the BPM I get the following error:
UpdateMaster.CommonTypes.cs(375,33): error CS0433: The type ‘LegalNumGenOptsTable’ exists in both ‘Erp.Contracts.BO.CustShip, Version=10.2.600.0, Culture=neutral, PublicKeyToken=5d3fa3c7105d7992’ and ‘Erp.Contracts.BO.Receipt, Version=10.2.600.0, Culture=neutral, PublicKeyToken=5d3fa3c7105d7992’
There are other errors but they are the same issue.
I found this mentioned in EpicCare and the current workaround is:
use a data directive BPM instead of a method directive BPM.
create an external DLL/assembly with the function you need there.
I’m going to explore the data directive approach but I was curious if anyone has found a way around this in a Method Directive other than creating an external DLL?
So was this an old bug that got resurrected? I think I read somewhere that it was supposed to be fixed in 10.2. I also saw a post about 10.x where someone described putting a post processing BPM on Quote.CreateOrder that uses the SalesOrder BO. You definitely cannot do that in 11.x. Just adding the reference for the SalesOrder BO causes the compiler error on save.
No, I think it was a design decision that didn’t take into account namespace. I would say that functions, which appeared in 10.2, moves us away from all of this in-process communications and to more of an API model. Now Directives can easily share code but also keep their datasets segmented. And since one can optionally expose them via REST, it’s a win-win.
This problem cannot be fixed with the current ICE framework and without completely breaking changes in most Epicor APIs.
The issue roots in the original decision to define tableset types and their table types on the “per contract assembly” base and to use identical namespaces there (Ice.Tablesets and Erp.Tablesets).
So, there is no fix and will no fix even midterm. But, a workaround with separation logic by different EFx libraries should work.
But if you decide to use both conflicting tableset types in one library, you face the same problem.
Since I find it more maintainable to write my BPMs as a single custom code widget, it occurred to me that maybe I was using ServiceRenderer wrong in my code and maybe the Invoke BO Method widget would work. But no. A method directive on Quote.CreateOrder post consisting only of an Invoke BO Method widget that calls SalesOrder.GetByID triggers the compilation error.
To be clear, you can use multiple BOs in the same Epicor Function that you cannot use in the same BPM. Maybe EFs sandbox each BO somehow.
In the same library? Can’t believe that. There is no type-isolation on the EFx library level.
If types have identical full names and are defined in different assemblies, they cannot be used in one project without global aliases. But global aliases are completely incontrollable in low-code environments.
I’m not actually using any of the conflicting types in BPMs or EFs. I’m using classes from the assemblies that contain the conflicting types. Namely, I’m using the Quote BO and the Sales Order BO, which contain conflicting definitions for tablesets that don’t seem related to either BO. And yes, I’m using these BOs in the same EF library and function with no issue.
You cannot do the same thing in BPMs. You cannot reference the Sales Order BO in any way, shape, or form in a method directive on any Quote BO method. Merely adding the reference to the assembly that defines the Sales Order BO contract without referencing any types from it triggers the compilation error, not when you save your code (i.e., there’s no syntax error) but when you save the Method Directive with “enabled” checked.
It means you are using conflicting types. Directly or indirectly.
Could you send me exported library? As the architect and main maintainer of BPM and EFx, I don’t see any differences between BPM directive and EFx function from this point of view.
How do you create tablesets to use Sales Order/Quote tablesets in one function?
In general, it is a completely different case. You are in the scope of Quote BO already. To compare with EFx you should use Salas Orde BO and Quote BO service in a method directive on another service. In such a case, you will have configuration similar to the EFx function/library.
Fair enough. But that’s not a useful solution if the logical place to use the SalesOrder BO is on a Quote method. For example, I wanted to make a change to the sales order that was created from a quote. The obvious place to do that is in a method directive on Quote.CreateOrder post, but that triggers the conflict. The OP’s case is another example of the conflict occurring between BOs that naturally flow together.
You’re right, you can can use the Quote and SalesOrder BOs together in a BPM on some other BO. I just tested it by writing a BPM on JobEntry.Update that calls SalesOrder.GetByID and Quote.GetByID, binding both datasets to variables. As you say, this is more analogous to my EF. This is pointless with BPMs because it means writing BPMs in silly places, but it’s useful with EFs because you can call them from BPMs that would have triggered the conflict.
Agree. I mentioned only that it was an improper comparison with EFx.
Unfortunately, when you work in the scope of Sales Order BO, its tableset types are in code already (e.g., as a part of method signature). So, a compiler cannot choose a proper type.
but it’s useful with EFs because
In EFx you can get a problematic configuration like in BPM too. For example, if you add Sales Order tableset as a function parameter, and then will try to call Quote BO.
Can’t external aliases fix this? Exactly as the thick client customization toolset does to get around the same issue?
When I was a consultant, I ran into this same issue for something PO related and my solution was to simply trigger another BPM to complete the work from the first (10.2.300 prior to efx).
It is completely unmanageable in low-code environments.
Moreover, global aliases were invented for the scenarios when .NET project has references to the FOREIGN libraries with conflicting type names, not for the cases like we have.
@joerojas I have run into this as well. The first time I ran into it I went the external DLL route, which did indeed work, but it is a pain to maintain that DLL with each patch you apply to Epicor. The next time I ran into this I decided to go the data directive route but still initiated from the method directive since I wanted to really isolate when the logic fired. This is a bit of a hack, but it works:
I’ve still got my method directive in place along with a bunch of conditions, prompts, etc. that I use to determine if I want to create the PO from the SO. In my case the method directive is on SalesOrder.MasterUpdate. When I get to the point where I want to initiate that PO to get created I simply set a field value my data directive is monitoring (in my case, OrderHed.UserChar2 is to be set to “CREATE PO”).
A data directive is sitting on OrderHed looking for that value and it calls the PO business objects with no issues.