Converted Code Programming Guide 2021.1
Converted Code Programming Guide 2021.1
C# Programming Guide
(conversion from ABL)
Classic
Disclaimer
This document is for informational purposes only and is subject to change without notice. This document and its
contents, including the viewpoints, dates and functional content expressed herein are believed to be accurate as of its
date of publication. However, Epicor Software Corporation makes no guarantee, representations or warranties with
regard to the enclosed information and specifically disclaims any applicable implied warranties, such as fitness for a
particular purpose, merchantability, satisfactory quality or reasonable skill and care. As each user of Epicor software is
likely to be unique in their requirements in the use of such software and their business processes, users of this document
are always advised to discuss the content of this document with their Epicor account manager. All information contained
herein is subject to change without notice and changes to this document since printing and other important information
about the software product are made or published in release notes, and you are urged to obtain the current release
notes for the software product. We welcome user comments and reserve the right to revise this publication and/or
make improvements or changes to the products or programs described in this publication at any time, without notice.
The usage of any Epicor software shall be pursuant to an Epicor end user license agreement and the performance of
any consulting services by Epicor personnel shall be pursuant to Epicor's standard services terms and conditions. Usage
of the solution(s) described in this document with other Epicor software or third party products may require the purchase
of licenses for such other products. Where any software is expressed to be compliant with local laws or requirements
in this document, such compliance is not a warranty and is based solely on Epicor's current understanding of such laws
and requirements. All laws and requirements are subject to varying interpretations as well as to change and accordingly
Epicor cannot guarantee that the software will be compliant and up to date with such changes. All statements of
platform and product compatibility in this document shall be considered individually in relation to the products referred
to in the relevant statement, i.e., where any Epicor software is stated to be compatible with one product and also
stated to be compatible with another product, it should not be interpreted that such Epicor software is compatible
with both of the products running at the same time on the same platform or environment. Additionally platform or
product compatibility may require the application of Epicor or third-party updates, patches and/or service packs and
Epicor has no responsibility for compatibility issues which may be caused by updates, patches and/or service packs
released by third parties after the date of publication of this document. Epicor® is a registered trademark and/or
trademark of Epicor Software Corporation in the United States, certain other countries and/or the EU. All other
trademarks mentioned are the property of their respective owners. Copyright © Epicor Software Corporation 2021.
All rights reserved. Not for distribution or republication. Information in this document is subject to Epicor license
agreement(s).
Classic
Revision: June 08, 2021 12:27 a.m.
Total pages: 56
sys.ditaval
C# Programming Guide (conversion from ABL) Contents
Contents
Introduction............................................................................................................................5
ERP and ICE Tables..................................................................................................................6
Case Sensitivity.......................................................................................................................7
Case Sensitivity in String Comparisons.............................................................................................................7
Compilation Errors...........................................................................................................................................7
Calling Method From Business Object..................................................................................9
Calling BOs From Custom Code...........................................................................................10
Calling Method From Standard Assembly..........................................................................11
Accessing ERP Context From Within System Directive......................................................12
Displaying an Informational Message................................................................................13
Throwing Exceptions............................................................................................................14
Writing Messages to Log.....................................................................................................15
Compatibility Class...............................................................................................................16
Replacing Resulting Dataset................................................................................................17
BufferCopy Considerations..................................................................................................18
Working With Custom Data.................................................................................................19
Create User-Defined Fields.............................................................................................................................19
Access User-Defined Fields.............................................................................................................................21
Loading User-Defined Data Into Various Objects............................................................................................22
Transition Path...............................................................................................................................................22
Obsolete ABL Code...............................................................................................................24
Using External Libraries.......................................................................................................25
Create Project in Visual Studio.......................................................................................................................25
Call External Assembly Using BPM..................................................................................................................26
Debugging Using Visual Studio...........................................................................................29
Prerequisites..................................................................................................................................................29
Debug BPM Directive.....................................................................................................................................29
Debug Custom Project...................................................................................................................................32
Adding and Subtracting Date..............................................................................................35
Subtracting Two Dates and Comparing to an Integer Value............................................36
Migrating ABL Expressions..................................................................................................37
ABL Find Last Conversion.....................................................................................................38
Using Unassigned Local Variable Message.........................................................................39
Epicor 10 Equivalent to Row Mod = A or U........................................................................40
Converting a Number to a String in LINQ Expression........................................................41
Outputting Data to a File.....................................................................................................42
Updating Database Tables...................................................................................................44
Introduction
The main perspective of the guide is aimed at ABL code users from previous Epicor ERP releases. It provides
guidelines on what corrections and modifications users may need to perform when converting snippets of Progress
ABL code to the .NET C# language used by the Epicor ERP 10 framework. ERP users that have only ever used ERP
version 10 and later can also benefit from seeing the C# examples in this guide.
Important Epicor highly recommends you recreate obsolete directives utilizing ABL code using the standard
BPM functionality and only use custom directives for specific requirements not achieved by the out-of
the box BPM features. Using standard BPM functionality ensures easier maintenance and upgrade of your
directives across Epicor ERP releases.
Tip For more information on the available BPM tools, please review the below Epicor ERP resources:
• Epicor ERP Help:
• System Management > Business Process Management
• Epicor ERP User Guides > Tools User Guide.
• Epicor ERP Help and Support center panel > Epicor Learning Center:
• Sign into your account, choose the ERP Product and search for the current on-demand and live BPM
course offerings.
Epicor programs belong to either the application system (ERP) or the tools system (ICE). In Epicor ERP 10, certain
schema changes were made. Make sure to reference the correct part of the Epicor application.
In the following example, a user-defined table references "ICE" schema, where it belongs.
Ice.Tables.UD01 UD01;
Tip You can use the Data Dictionary Viewer to find and review details of each field and table within
the database.
Case Sensitivity
By default, code written in C# treats string comparisons as case sensitive, which was not the case in ABL code.
Note When you convert a statement or create new custom code, be sure to take appropriate casing into
account.
Compilation Errors
Remember that C# is a case-sensitive language, and it may result in compilation errors. For example, look at the
simple below:
Notice that the error message has "customer" starting with a lower-case letter c.
var CustomerRecs = (from r in Db.customer
from r_UD in Db.Customer_UD
where r.SysRowID == r_UD.ForeignSysRowID &&
r.Company == Session.CompanyID &&
r.CustNum == ttOrderHedRow.CustNum
select r_UD).FirstOrDefault();
This query will cause the following error message when you try to compile the BPM:
In order for the BPM to compile without errors, change the first line of the query to use an upper-case letter C
as shown below:
var CustomerRecs = (from r in Db.Customer
from r_UD in Db.Customer_UD
In some cases, you need to call a method from a business object (BO).
For this purpose, use the ServerRenderer and add a reference to the contract containing the particular business
object. ServerRenderer is a helper class residing in Epicor.System.dll. You can use this class to make calls to any
application service with the defined contract. This class returns an instance of the contract, which you can use
to invoke any method available on the contract.
The signature of the method is displayed below:
/// <summary>
/// Returns a class instance for the given contract
/// </summary>
/// <param name="ignoreFacade">if set to <c>true</c> method returns instance of
service instead of service facade.
</param>
/// <typeparam name="TService">The contract type. The contract must have a Serv
iceContract attribute and the service namespace must follow the Ice naming conv
ention
</typeparam>
/// <returns>A class instance for the given contract
</returns>
public static TService GetService<TService>(bool ignoreFacade = false)
In the following example, the GetByID() method is called from the Tip Service. In order to make a call to the
TipService method from another directive, you first add TipService contract assembly to the reference, and then
make the call. The svc variable holds the Tip contract instance.
var svc = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.TipSvcContrac
t>(Db);
svc.GetByID(…);
If within one code scope (for example, one action) several calls are made to the same contract, it is recommended
to call ServiceRenderer just once. For example:
var svc = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.TipSvcContrac
t>(Db);
svc.GetByID(…);
...some other code here
svc.GetList(…);
...some other code here
svc.Update(…);
The following example displays a correct conversion of ABL code to C#.
• ABL Code:
DEF VAR opNextJobNum AS CHARACTER NO-UNDO.
DEF VAR vh-JobEntry AS HANDLE NO-UNDO.
RUN bo/JobEntry/JobEntry.p PERSISTENT SET vh-JobEntry.
RUN GetNextMntJobNum IN vh-JobEntry (OUTPUT opNextJobNum) NO-ERROR.
• C# Code:
string opNextJobNum = string.Empty;
Erp.Contracts.JobEntrySvcContract vh_JobEntry = null;
vh_JobEntry = Ice.Assemblies.ServiceRenderer.GetService<Erp.Contracts.JobEntr
ySvcContract>(Db);
vh_JobEntry.GetNextMntJobNum(out opNextJobNum);
Use the following approach when calling Business Objects from the Custom Code Action or Condition.
The proper way of performing a call to other BO is to dispose service instance retrieved through the ServiceRenderer.
Note When you retrieve a service instance through the ServiceRenderer, the service is called in a way that
triggers all BPM directives attached to the method being called. Therefore, if a directive calls the same
method it is attached to, the current directive and all other directives utilizing the same method should be
designed to avoid endless loops. For example:
Use the following steps when calling a method from another assembly (non-external assembly).
3. You can now construct the code that calls a method from another assembly. For example:
Erp.ErpContext DbErp = CallContext.Current.GetMainContext<Erp.ErpContext>()
;
Erp.Internal.MR.CPMethod _CPMethod = new Erp.Internal.MR.CPMethod(dbErp);
List<Erp.Internal.MR.TgtJob> tgtJobRows = new List<Erp.Internal.MR.TgtJob>
();
Guid p1 = Guid.Empty;
bool crossplant = false;
DateTime? copyDate = null;
string copyRev = string.Empty;
string copyJob = string.Empty;
int pCount = 0;
_CPMethod._CPMethod(p1, "", "", "", null, true, false, false, false, out c
rossplant,
out copyDate, out copyJob, out copyRev, ref pCount, ref tgtJobRows);
You can access Erp Context from within a system directive. For example, review the following code:
Erp.ErpContext DbErp = CallContext.Current.GetMainContext<Erp.ErpContext>();
In a BPM directive created for system BO "UD10", you need to access Part table which belongs to Erp context.
The code should look like the following:
Erp.ErpContext DbErp = CallContext.Current.GetMainContext<Erp.ErpContext>();
if ((from Part_Row in DbErp.Part
where string.Compare(Part_Row.Company, ttUD10_xRow.Company, true) == 0
select Part_Row).Any())
{
...
}
Note At the moment, the BPM context is either ErpContext (for application directives) or IceContext (for
system directives). Therefore, within the IceContext, you will not find certain ERP tables (for example, Part),
and you will need to use the process described above.
Epicor is working towards enhancing the BPM and making the default context unified. Once this is
implemented, the need of accessing another context within the BPM code will be eliminated.
Informational messages are useful when debugging BPMs and presenting non-exception type messages to the
users.
You can publish an informational message to the user from the execute custom code node in the BPM designer.
The syntax example is shown below:
this.PublishInfoMessage(Variable or String of the message,
Ice.Common.BusinessObjectMessageType.Information,
Ice.Bpm.InfoMessageDisplayMode.Individual, "YourBO", "YourMethod");
The last two parameters are optional, you can leave the strings empty.
Note that the possible values for message type are: Information, Question, Warning, Error, UpdateConflict. The
possible values for the InfoMessageDisplayMode are: Individual & Grid.
Example message was placed in vMiscInfo variable. Note that you should not use all capitalized "BPM" in the
reference to the Display Mode parameter, it should be "Bpm".
this.PublishInfoMessage(vMiscInfo, Ice.Common.BusinessObjectMessageType.Informa
tion,
Ice.Bpm.InfoMessageDisplayMode.Individual, "OrderHed", "ChangeNeedB
yDate");
Throwing Exceptions
Exceptions are used to indicate that an error has occurred while the program is running.
The following example displays how you can throw a business exception from within the code:
throw new Ice.BLException("My exception");
Note Within the BPM Workflow Designer, the Raise Exception workflow element is the preferred way of
throwing exceptions.
This topic discusses how you can write a message to the server log.
In Epicor 9, the following syntax was used:
Message "test1" "test2".
In Epicor ERP 10, writing to the server log is approached using the following syntax:
Ice.Diagnostics.Log.WriteEntry("TEST1" + " " + "test2");
Compatibility Class
If the Compatibility class is present in the converted code, you need to add a reference to Compatibility.dll, which
can be found in the Assemblies folder.
This class contains conversion routines and helper methods to comply with Progress ABL code functionality.
• ABL Code:
FIND FIRST ttUD08 NO-LOCK NO-ERROR.
IF Available ttUD08 THEN DO:
DEFINE VAR va-value AS INTEGER NO-UNDO.
ASSIGN va-value = INTEGER(ttUD08.Key3) NO-ERROR.
IF error-status:error THEN ASSIGN ttCallContextBpmData.Character01 = "invali
d".
END.
• C# Code:
var ttUD08_xRow = (from ttUD08_Row in ttUD08 select ttUD08_Row).FirstOrDefaul
t();
if (ttUD08_xRow != null)
{
int va_value = 0;
try
{
Compatibility.ErrorStatus.Clear();
va_value = System.Convert.ToInt32(ttUD08_xRow.Key3);
}
catch (Exception e)
{
Compatibility.ErrorStatus.SetError(e);
}
if (Compatibility.ErrorStatus.Error)
{
this.callContextBpmData["Character01"] = "invalid";
}
}
This topic describes how to replace your directive's resulting record with another record.
For example, you may have a method directive action on the GetByID() method which returns a Part ID. The usual
BPM procedure provides the directive code with retrieved Part record, which is held in the ttPart table. If, for
some reason, you want to replace the whole resulting record with another record that you may have retrieved
yourself using another method, then you have the following options:
• Set the values on ttPart
• Replace the whole dataset
Note Fetching the dataset and replacing the whole dataset using the Attach method eliminates the need
of writing a lot of code. This is especially useful when the resulting dataset contains several tables.
In the following example, the record is retrieved using a sample method FetchRecord() and the result is replaced
with myRec.
var myRec = myService.FetchRecord(id);
this.dsHolder.Attach(myRec);
BufferCopy Considerations
The following section describes what you should consider when working custom columns and their usage within
the BPM functionality.
In the previous versions of the Epicor application, each table included a series of user-defined fields, such as
Character01, Date03, Number02. While these additional columns were mostly sufficient for customizations,
some users ran out of available columns. Then, during an upgrade, these user-defined fields were overwritten,
so users needed to export customizations and, after the upgrade was complete, import them back into the
application. This process sometimes required users to additionally update the tables/columns to make the
customization compatible with the new application version. In other Epicor installations, the predefined user-defined
columns might not have been used at all, and were causing unnecessary system load.
To address these issues, the Epicor application now incorporates this functionality as a database extension. If you
are building a BPM directive that needs unique fields, you create user-defined tables and add columns to these
tables. Since these tables are extensions from parent tables, they are not overwritten during an upgrade.
In Epicor ERP 10, use the following process to create user-defined fields.
Note The existing Epicor 9.05 user-defined columns that were utilized (contained data) are automatically
migrated to Epicor ERP 10.
1. Create the user-defined tables and columns (fields) within the Epicor application. To do this, navigate to
System Setup > System Maintenance and open User Defined Column Maintenance.
2. Find and select the table you need to extend. Click Save. The application adds a "_UD" suffix to the end
of the table identifier.
3. Add the needed custom columns to the user-defined table that you have created.
When you save each column, the application adds a "_c" suffix to the end of this table column. This suffix
will help you identify this custom column when you create BPM directives that reference this column.
4. Add the user-defined table to the database. To do this, regenerate the data model for the Epicor database.
You or your system administrator runs this task on the server that hosts your database. The data model is
regenerated using the Epicor Administration Console.
Important To regenerate data model and recycle application pools in Epicor Cloud ERP (Dedicated
Tenant) environment, you must promptly submit a request via an EpicCare case and specify the
location of the newly added user-defined data (table/column) - System, Live, or Pilot, - and the best
time the Cloud Operations team can run these processes.
As a result, the base and the user-defined tables are joined in the data model.
5. Now to complete this process, you must pull the latest data model from the database and copy it to the
local application server by recycling the application pool. Recycling the application pool is a mandatory task
after the data model successfully regenerates. To do this, click Start > Programs > Administrative Tools
> Internet Information Services (IIS) Manager.
7. Right-click on the application pool for your application server; from the context menu, select Recycle.
Tip Optionally you can also recycle the application pool within the Epicor Administration Console.
To do this, expand the Server Management node and select your application server. From the
Actions pane, select the Recycle IIS Application Pool option.
The user-defined table and columns are now fully integrated with the database. You can now monitor and update
these custom columns through BPM directives.
Tip Unlike previous versions of the Epicor application, running the Directive Update program and Refreshing
Signatures of directives referencing affected table(s) with UD columns is no longer needed. This process is
performed automatically.
This topic describes how you can reference user-defined columns within BPM in Epicor ERP 10.
1. User defined tables and primary tables merge into one for the database context. When you construct a Data
or Method Directive workflow, user-defined columns display as part of the base table.
2. When you design a business activity query (BAQ) in BPM from within Data and Method Directives, user-defined
columns also appear as part of the base table.
3. When working with dataset tables for Data Directives, user-defined fields appear as part of the primary
table, and can be directly accessed. For example:
ttABCCode.MyNewColumn_c
4. Method directives work with dataset tables using service method parameters of tableset type, and so you
access them through different syntax. These tableset parameters are defined in the business object's (BO)
contract .dll file, so their format is fixed and they are not regenerated with the data model. When building
a method directive that refers to a user-defined field in a dataset table, reference it using the following
syntax: <MethodParameterName>.<TableName>.["UDField<DataType>(UDFieldName)"]. For example:
ds.ABCCode.UDField<System.String>("MyNewColumn _c")
This topic discusses available extensions you can use to load User-Defined (UD) Data into various objects.
Note UD data is loaded automatically into table entities when data is retrieved from the database. You
may only need to manually load UD data into IceTablesets, IceTables or IceRow. Examples are below:
• IceRow:
Ice.Tablesets.TipRow iceRow = new TipRow();
…
iceRow.LoadExtendedData(Db);
• IceTable:
Ice.Tablesets.TipTable iceTable = new TipTable();
…
iceTable.LoadExtendedData(Db);
• List<IceRow>:
List<IceRow> list = new List<IceRow>();
…
list.LoadExtendedData(Db);
• IceTableset:
Ice.Tablesets.TipTableset ts = new TipTableset();
ts.LoadExtendedData(Db);
Transition Path
This topic explains actions you need to take when you migrate UD Fields from Epicor ERP 10 Beta code prior to
10.0.600 into Epicor ERP version 10.0.600 or later.
Note You only need to perform the following steps if you are migrating from Epicor ERP 10 Beta prior to
version 10.0.600.
1. As part of the installation process, Regeneration of the Data Model is performed. This ensures the UD fields
are included in the data model.
2. The next step involves running mandatory BPM conversion. The conversion process upgrades BPM directives
to follow the new extended data approach.
3. After you log into the application, Epicor recommends to review the following:
• BPM directives that have become Outdated (as it is usual when upgrading from older builds)
• BPM directives that are known to contain UD Field references in queries or custom codes. It is highly
recommended to review these directive to ensure their performance is optimized in the new environment.
Example
• Previously for database queries a join between the user-defined table and base table was used,
for example:
Ice.Tip join Ice.Tip_UD on t.SysRowID = tu.ForeignSysRowID
The migration process is likely to convert such code as follows:
Ice.Tip join Ice.Tip_UD on t.SysRowID = tu.SysRowID
If the above code compiles, it is sub-optimal. Because the data model now merges UD Table
columns with the respective base table automatically, you should refer to the Tip entity object only
to eliminate references or joins to Tip_UD altogether.
• Custom code performing [loop on Tip from the DB] with inner [loop on Tip_UD from the DB]
This code will be converted to [loop on Tip from the DB] with inner [loop on Tip from the DB]. The
most optimal way is [loop on Tip from the DB], however the body of the loop will need to be
adjusted.
4. When importing BPM directives referencing UD fields created in the code prior to 10.0.600, these directives
are also automatically converted and should be reviewed for optimal performance.
Certain calls that existed in Epicor 9.05 became obsolete in Epicor ERP 10 and need to be removed.
For example, you may have an Epicor 9.05 BPM directive that uses an ABL action with code that calls
lib\UpdateTableBuffer.p.
To avoid compilation errors when converting such directive, remove the below call in your converted code:
lib\UpdateTableBuffer.p
Please consider the following information when selecting the approach for migrating external .p routines from
Epicor 9.05 to Epicor ERP 10.
In certain cases when Epicor 9.05 customizations contained many calls to external .p progress code files, it may
be reasonable to combine these .p routines into a single .NET assembly (library) of external methods. These
methods can then be called from within a BPM flowchart using the Invoke External Method BPM element.
You can also consider creating an external library, when you called a method in the Epicor 9.05 external .p file,
which subsequently invoked other methods within the same or different .p code files.
However, if the external .p contained simple code in one or few methods, it is recommended to convert such
code into a snippet and incorporate it into the BPM Execute Custom Code element, rather than creating an
external library assembly. Please note that this approach may be preferred as:
• It does not require .NET Visual Studio
• It does not require maintaining and updating external assemblies
The following section describes how to create a new project using the Visual Studio.
In the example below, the project utilizes the Update Method Directive for Tip Service.
1. Create an empty C# Class Library Type Project. In this example, we create the project called "ExternalBpm".
Note that the Ice.Contracts.BO.Tip is added, as we are going to make a call from Tip Update BPM Method
Directive.
Important Make sure to set "Copy Local=False" for all added references.
3. The project adds the Class by default. You can rename it, if you want. In this example, MyTip.cs is used.
1. In your Epicor EPM 10 application, open Method Directives. To do this, navigate to System Management
> Business Process management > Method Directives Maintenance.
4. The Programming Interface Generator Form window displays the method signature.
5. Copy the whole code (or method, if you already have other methods there).
6. Add your logic into the method and compile the project.
7. Place the library assembly file to the folder for external assemblies. Usually, it is the folder located within
the Server\Customization\Externals folder, but this setting can be changed in the application's
web.config, within the CustomizationSettings property.
8. You can now use this library and call it using the Invoke External Method workflow element.
The external Update method from ExternalBpm assembly will now be called by the BPM.
Tip For more detailed example, review the Custom Business Process Management chapter found
within the Epicor ICE 3.0 Tools User Guide.
If you have Microsoft® Visual Studio™ 2010 or higher, you can debug execution of custom code directives.
Debugging can be particularly useful when you need to review execution of a complex custom code.
Prerequisites
This topic discusses steps you need take to before you start debugging.
The Epicor Customization Framework (ECF) supports two ways of storing generated assemblies. The preferred
method, which is either SQL BLOB (Binary Large Object) or File System Storage is defined in the Epicor ERP 10
web.config file within the customizationStorage provider property.
Before you start debugging, do the following:
• In order to load the program database (pdb) file that holds debugging symbols, verify the loadPdb property
found in the web.config is set to true.
loadPdb ="true"
• Verify the intermediateFolder, where directive sources are generated contains in a valid path. For example:
intermediateFolder="C:\_projects\2012R\Current\Deployment\Server\BPM">
Example Your web.config settings may look as follows:
<customizationSettings
loadPdb ="true"
disabled="false"
intermediateFolder="C:\_projects\2012R\Current\Deployment\Server\BPM">
• To reload customization assembly and debug symbols, restart IIS. Alternatively, only restart the Epicor ERP
application pool.
This topic explains how you can debug customization assembly compiled by the Epicor Customization Framework.
1. By default, sources are found in the BPM folder of the Server directory.
Note A different sources folder can be specified using the intermediateFolder attribute in the server
web.config file.
Make sure that the folder specified in the intermediateFolder attribute exists at the time IIS AppPool
used by the Epicor 10 application starts. Also, verify the account used by that AppPool has read and
write access to that folder. Otherwise, the setting is ignored and sources are saved in the system's
TEMP folder.
Important New sources are generated each time you save the directive.
3. Make sure you are working with the latest BPM sources when debugging a directive. Drag and drop all files
into the Microsoft Visual Studio.
4. For debugging Options, make sure the Enable Just My Code and Require source files to exactly match
the original version options are clear.
5. Attach the debugger to the w3wp.exe process the application pool is running under.
7. Run the routine in the Epicor client. When the BPM customization is fired, the breakpoint is activated and
you can verify each step in the Visual Studio.
c. Launch the Epicor client again and regenerate the directive to update directive sources.
Tip By default, IIS7 app pool can only use 90 seconds for a non-responsive application. During IIS
web application or website debugging time, you may want to change its corresponding application
Pool advanced setting's "Ping Maximum Response Time" to a time much longer, or turn off "Ping
Enabled" setting.
This topic outlines how you can debug custom project or solution created in Visual Studio.
In this example, a project is used to define the programming logic for a custom AbcCode.GetList() external method
written in C# .NET.
2. Invoke the .NET external method you created using a directive. In this example, a post-processing directive
for ABC.GetList BO method is used.
3. In Visual Studio, attach the debugger to the w3wp.exe process the application pool is running under.
4. Set the breakpoint in the custom code and run the routine you want to debug in the Epicor client.
5. At this point the debugger stops at the specified break point and you can the follow code execution, examine
variable values and so on.
To add or subtract days from a given date field, use the syntax as displayed below. Note that subtraction is done
by adding a negative number of days.
In the following example, 6 days are substracted from NeedByDate:
ttOrderHedRow.NeedByDate.Value.AddDays(-6)
Use the following syntax to subtract one date from another and then compare the number of days difference to
an integer value.
Difference and Days are variable names and can be replaced with any variable name of your choice.
var difference = DateTime.Today - PartTran.TranDate;
var days = difference.Value.Days;
if ( days > 90)
The BPM Migration Tool is capable of migrating most of the valid Epicor 9.05 ABL expressions.
To ensure a successful migration of expressions, verify the following:
• ABL expressions are functional in your Epicor 9.05 application.
• All tables, arguments and functions used in expressions are known.
This topic discusses code adjustments when converting ABL Find Last statement.
Assume the below statement exists in a BPM.
FIND LAST Parttran where
Parttran.company = CUR-COMP and
Parttran.partnum = Quotemtl.partnum and
(Parttran.trantype = "STK-MTL" OR
Parttran.trantype = "MFG-STK" OR
Parttran.trantype = "PUR-MTL" OR
Parttran.trantype = "MFG-CUS" )
no-lock no-error.
Below is an example of a converted statement. Note that OrderBy clause is used to sort the data. In this example,
the last record becomes the first record in the returned rows:
PartTran = (from PartTran_Row in Db.PartTran
where PartTran_Row.Company == Session.CompanyID &&
string.Compare(PartTran_Row.PartNum, QuoteMtl.PartNum, true) == 0
&&
(string.Compare(PartTran_Row.TranType, "STK-MTL", true) == 0 ||
string.Compare(PartTran_Row.TranType, "MFG-STK", true) == 0 ||
string.Compare(PartTran_Row.TranType, "PUR-MTL", true) == 0 ||
string.Compare(PartTran_Row.TranType, "MFG-CUS", true) == 0)
orderby PartTran_Row.TranDate descending
select PartTran_Row).FirstOrDefault();
Within the BPM logic, it may happen that a variable is not set. In that case, the compiler displays an error message,
reporting use of an unassigned local variable.
Review the following example:
Erp.Tables.Customer Customer;
Erp.Tables.Customer_UD Customer_UD;
if (Customer != null)
Customer_UD.CheckBox01 = true;
Customer.CreditLimit = 0;
Customer.CreditHold = false;
Db.Validate();
txscope.Complete();
}
}
This code will generate the following error upon compilation:
Server Side Exception
There is at least one compilation error.
Exception caught in: Epicor.ServiceModel
Error Detail
============
Description: There is at least one compilation error.
Details:
Error CS0165: Use of unassigned local variable 'Customer_UD'
Notice that in the code example, the Customer_UD variable is only set when the following condition is met:
if (Customer != null)
You can correct the error by changing the second line of the example to:
Erp.Tables.Customer_UD Customer_UD = null;
This change ensures that the variable gets set to some value regardless of the IF statement.
In many BPMs a user wants to perform a certain action if the tt record is a new record or updated record. In
Epicor 9.05 the status of the RowMod = 'A' or 'U' was used in this case. The Epicor 10 equivalent is displayed
below:
foreach (var ttAPInvHed_iterator in (from ttAPInvHed_Row in ttAPInvHed
where (string.Equals(ttAPInvHed_Row.RowMod, IceRow.ROWSTATE_ADDED,
StringComparison.OrdinalIgnoreCase) ||
string.Equals(ttAPInvHed_Row.RowMod, IceRow.ROWSTATE_UPDATED,
StringComparison.OrdinalIgnoreCase))
select ttAPInvHed_Row))
The syntax shown below can be used for BPMs that write data out to a file. You can create a new file as in the
example below, or replace Create with Append to append to an existing file.
using (var MyFile = new System.IO.StreamWriter(new System.IO.FileStream(Path to
file goes here), System.IO.FileMode.Create))
{ // MyFile Scope starts
MyFile.WriteLine( The data you wish to export goes here)
} // MyFile Scope ends
The relevant syntax is bolded in the example below:
using (var MyFile = new System.IO.StreamWriter(new
System.IO.FileStream(Company.UDField<string>( "Character01"),
System.IO.FileMode.Create)))
{ // MyFile Scope starts
if (!String.IsNullOrEmpty(OrderList))
{
for (i = 1; i <= OrderList.NumEntries("~"); i++)
{
foreach (var OrderRel_iterator in (from OrderRel_Row in Db.OrderRel
where OrderRel_Row.Company == Session.CompanyID
&& OrderRel_Row.OrderNum ==
System.Convert.ToInt32(OrderList.Entry(i - 1, '~'))
&& OrderRel_Row.NeedByDate >= FromDate
&& OrderRel_Row.NeedByDate <= ToDate
select OrderRel_Row))
{
OrderRel = OrderRel_iterator;
PartNum = OrderRel.PartNum;
PlantID = OrderRel.Plant;
Balance = OrderRel.OurStockQty - OrderRel.OurStockShippedQty;
if (Balance > 0)
{
Quantity = Balance.ToString("99999999");
}
OutOrderNum = OrderRel.OrderNum.ToString("99999999999");
LineNum = OrderRel.OrderLine.ToString("999");
if (Balance > 0)
{
foreach (var PlantWhse_iterator in (from PlantWhse_Row in
Db.PlantWhse
where PlantWhse_Row.Company ==
Session.CompanyID
&&
string.Compare(PlantWhse_Row.WarehouseCode, "13120", true) == 0
&&
string.Compare(PlantWhse_Row.PartNum, PartNum, true) == 0
&&
string.Compare(PlantWhse_Row.PrimBin, "CARMAN", true) == 0
select PlantWhse_Row))
{
PlantWhse = PlantWhse_iterator;
MyFile.WriteLine(@" ; ; ; ; ;" + PartNum + ";" +
Quantity + "; ; ; ; ; ;
; ;SO" + OutOrderNum + ";" + OutOrderNum + ";" + LineNum + "; ;" +
PlantID + ";CARMAN ;");
}
}
}
}
}
else
{
foreach (var OrderRel_iterator in (from OrderRel_Row in Db.OrderRel
where OrderRel_Row.Company == Session.CompanyID
&& OrderRel_Row.NeedByDate >= FromDate &&
OrderRel_Row.NeedByDate <= ToDate
select OrderRel_Row))
{
OrderRel = OrderRel_iterator;
PartNum = OrderRel.PartNum;
PlantID = OrderRel.Plant;
Balance = OrderRel.OurStockQty - OrderRel.OurStockShippedQty;
if (Balance > 0)
{
Quantity = Balance.ToString("99999999");
foreach (var PlantWhse_iterator in (from PlantWhse_Row in
Db.PlantWhse
where PlantWhse_Row.Company ==
Session.CompanyID
&&
string.Compare(PlantWhse_Row.WarehouseCode, "13120", true) == 0
&&
string.Compare(PlantWhse_Row.PartNum, PartNum, true) == 0
&&
string.Compare(PlantWhse_Row.PrimBin, "CARMAN", true) == 0
select PlantWhse_Row))
{
PlantWhse = PlantWhse_iterator;
MyFile.WriteLine(@" ; ; ; ; ;" + PartNum + ";" +
Quantity + "; ; ; ; ; ; ;
;SO" + OutOrderNum + ";" + OutOrderNum + ";" + LineNum + "; ;" +
PlantID + ";CARMAN ;");
}
}
}
}
} // MyFile Scope ends
This example shows how to scope the transaction for updating the InvcDetail_UD table's Number 01 field.
The Using statement starts the transaction, the Db.Validate statement does the update, and the txscope statement
ends the transaction. Note that the select query adds a With(LockHint.UpdLock) phrase to the select statement
for the table to be updated (the InvcDtl_UD table in this example).
using (var txscope = IceDataContext.CreateDefaultTransactionScope())
{
var InvcDtlRecs = (from InvcDtl_UD_Row in Db.InvcDtl_UD.With(LockHint.UpdLock)
{
InvcDtlRecs.Number01 = OrderDtlRecs.Number
01;
Db.Validate();
}
}
txscope.Complete();
}
Epicor 9.05, ABL Code was calling a .p file that called the UD14 business object and added certain data to it. In
Epicor 10, the BPM was modified to call the business objects and the .p code moved into the BPM's Execute
Custom Code action.
• Epicor 9.05 ABL Code:
FOR EACH ttOrderHed:
RUN DSTICustom\BPMLive\SalesOrder\StoreSORepsInUD14.p (input ttOrderHed.Order
Num, input ttOrderHed.SalesRepList,
input ttOrderHed.RepSplit1, input ttOrderHed.RepSplit2, input ttOrderHed.RepS
plit3, input ttOrderHed.RepSplit4,
input ttOrderHed.RepSplit5, input ttOrderHed.RepRate1, input ttOrderHed.RepRa
te2, input ttOrderHed.RepRate3,
input ttOrderHed.RepRate4, input ttOrderHed.RepRate5).
END.
{&TRY_PRIVATE}
define variable morePages as logical.
define variable i as integer.
define variable hUD14 as handle.
RUN bo\UD14\UD14.p PERSISTENT SET hUD14.
{&CATCH_PRIVATE}
int iRepSplit5 = 0;
decimal dRepRate1 = decimal.Zero;
decimal dRepRate2 = decimal.Zero;
decimal dRepRate3 = decimal.Zero;
decimal dRepRate4 = decimal.Zero;
decimal dRepRate5 = decimal.Zero;
bool morePages = false;
int i = 0;
Ice.Contracts.UD14SvcContract hUD14 = Ice.Assemblies.ServiceRende
rer.GetService<Ice.Contracts.UD14SvcContract>(Db);
if (hUD14 != null)
{
output_dataset_UD14DataSet = hUD14.GetRows("Key1 = '" + Syste
m.Convert.ToString(iOrderNum) + "'", 0, 0, out morePages);
/* Delete existing rows for this sales order before rebuildin
g */
foreach (var ttUD14_xRow in output_dataset_UD14DataSet.UD14)
{
var ttUD14Row = ttUD14_xRow;
hUD14.DeleteByID(ttUD14Row.Key1, ttUD14Row.Key2, ttUD14Ro
w.Key3, ttUD14Row.Key4, ttUD14Row.Key5);
}
/* rerun to clear out temp tables */
output_dataset_UD14DataSet = hUD14.GetRows("Key1 = '" + Syste
m.Convert.ToString(iOrderNum) + "'", 0, 0, out morePages);
for (i = 1; i <= cRepList.NumEntries("~"); i++)
{
hUD14.GetaNewUD14(ref UD14DataSet);
foreach (var ttUD14_iterator in (from ttUD14_Row in outpu
t_dataset_UD14DataSet.UD14
where string.Equals(ttUD
14_Row.RowMod, IceRow.ROWSTATE_ADDED, StringComparison.OrdinalIgnoreCase)
select ttUD14_Row))
{
var ttUD14Row = ttUD14_iterator;
ttUD14Row.Key1 = System.Convert.ToString(iOrderNum);
ttUD14Row.Key2 = System.Convert.ToString(i);
ttUD14Row["ShortChar01"] = cRepList.Entry(i - 1, '~')
;
ttUD14Row["Number01"] = iOrderNum;
if (i == 1)
{
ttUD14Row["Number02"] = iRepSplit1;
ttUD14Row["Number03"] = dRepRate1;
}
else if (i == 2)
{
ttUD14Row["Number02"] = iRepSplit2;
ttUD14Row["Number03"] = dRepRate2;
}
else if (i == 3)
{
ttUD14Row["Number02"] = iRepSplit3;
ttUD14Row["Number03"] = dRepRate3;
}
else if (i == 4)
{
ttUD14Row["Number02"] = iRepSplit4;
ttUD14Row["Number03"] = dRepRate4;
}
else if (i == 5)
{
ttUD14Row["Number02"] = iRepSplit5;
ttUD14Row["Number03"] = dRepRate5;
}
}
hUD14.Update(ref UD14DataSet);
}
}
else
{
Ice.Diagnostics.Log.WriteEntry("Could not create handle to UD
14.p!");
}
//hUD14.Dispose();
}
The following is an example of a C# code that sends email. This code has been generated through the BPM
wizard; you can use it within the BPM Custom Code action.
var mailer = this.GetMailer(async: true);
var from =
"[email protected]";
message.SetFrom(from);
var to =
"[email protected]";
message.SetTo(to);
var cc = "";
message.SetCC(cc);
var subject =
"subject line";
message.SetSubject(subject);
var body =
"E-mail message can include scalar and table parameters of the
business method";
message.SetBody(body);
mailer.Send(message);
The ICE Framework leverages the Entity Framework that handles all the data connections and represents all
database tables as objects off the Database Context.
The Database object is named Db. Tables are variables off of the Db object which can be used to construct LINQ
queries.
Example
BPM custom code can access the payload variables as defined in the CommonTypes file.
There are no "record centric" variables, only set based variables so you will need to query into the sets accordingly.
Example
A service can be accessed via it's contract either from a client or a service.
The service is accessed via the ServiceRenderer helper.
//Get the other service via it's contract
var tipBO =
Ice.Assemblies.ServiceRenderer.GetService<Ice.Contracts.TipSvcContract>();
Calling other Services requires referencing the other service contract assembly.
Example
Use the References sheet to add the assembly containing the service to be called, for example
Ice.Contracts.BO.UserFile.dll.
You then create the other service via its contract and call it as per the below example.
//Get the other service via it's contract
var userFileBO =
Ice.Assemblies.ServiceRenderer.GetService<Ice.Contracts.UserFileSvcContract>();
Using RowMod
All temp tables contain a column called RowMod at the bottom of the record. This property defines if a record
has been Added, Updated, Changed, or Deleted.
In Epicor 9, RowMod = "A", "U", "D" or "" indicated the action performed against the Row.
The Epicor 10 equivalent may look as follows:
foreach (var ttAPInvHed_iterator in (from ttAPInvHed_Row in ttAPInvHed
where (string.Equals(ttAPInvHed_Row.RowMod, IceRow.ROWSTATE_ADDED, StringCompar
ison.OrdinalIgnoreCase) || string.Equals(ttAPInvHed_Row.RowMod, IceRow.ROWSTATE
_UPDATED, StringComparison.OrdinalIgnoreCase))
select ttAPInvHed_Row))
However, a better shorthand ways are available for use by utilizing the Unchanged() Added() Deleted() or Updated()
methods, for example:
foreach (var ttAPInvHed_iterator in (from ttAPInvHed_Row in ttAPInvHed
where ( ttAPInvHed_Row.Added() || ttAPInvHed_Row.Updated() )
select ttAPInvHed_Row))
The above code can even be written as:
foreach (var ttAPInvHed_iterator in (from ttAPInvHed_Row in ttAPInvHed
where ( ! ttAPInvHed_Row.Unchanged() )
select ttAPInvHed_Row))
As of Epicor ERP 10.1, the naming convention for workflow items within BPM sources has changed.
The naming pattern now looks as follows:
A for action or C for condition + internal element number with leading zeros (e.g. 001) + underscore +
Action/Condition type name
Example
this.UseDataFilter = true;
this.A001_RaiseExceptionAction();
this.RefreshData(matched: false);