The statement-by-statement translation from Epicor's C# BPM code and widget logic to X++ — ICE business objects to table buffers, BPM condition widgets to where clauses, custom C# service calls to X++ select statements — and what happens when a dynamically configured directive becomes a compiled, statically typed extension class.
The previous chapter was about the estate — what you have, what it maps to, and how to triage it. This chapter is about the code you write on a Tuesday afternoon: the statements, the types, the loops, the transactions.
Key differences from C#: no namespace keyword (models provide the namespace equivalent), no using statements (all types in referenced models are visible), boolean instead of bool, and str instead of string. The new method is the constructor.
Three differences and nothing else. firstonly limits to one row. The where clause uses == for equality and && for conjunction. There is no .Context<>(), no LINQ lambda, no FirstOrDefault() — the query is a language statement, not a method chain.
The single biggest performance difference between Epicor BPM C# and X++ is the availability of set-based operators. Epicor BPMs process rows one at a time — there is no BPM-native batch-update mechanism. X++ provides three set-based operators that execute as single SQL statements:
Epicor BPM directives inherit the business-object's transaction implicitly — you never see a begin or commit in BPM code. X++ makes transaction boundaries explicit:
```xpp [ExtensionOf(classStr(SalesTableType))] final class SalesTableType_MyExtension { public void validateWrite() { // Pre-processing: additional validation before standard logic SalesTable salesTable = this.salesTable();
Epicor BPMs and Functions can run long-duration logic inside the transaction — constrained only by server timeouts. In D365, long-running logic belongs in the batch framework, not inside a CoC extension:
You can reference .NET assemblies from X++ (System.Net.Http, Newtonsoft.Json, custom .dlls). X++ classes are .NET classes at runtime — they have the same memory model, garbage collection and threading behaviour. Debugging uses Visual Studio with the same breakpoint, watch and call-stack experience as C#.
Epicor uses BPM directives and Functions to inject behaviour dynamically. D365 provides a similar plug-in pattern through SysExtension:
X++ is closer to C# than it first appears, but the differences are structural rather than syntactic. The embedded SQL, the explicit transactions, the set-based operators and the extension constructs are not variations on C# patterns — they are a different development philosophy built around a sealed application layer and a compiled,…