C# and BPM logic translated into X++

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.

What you will be able to do

Introduction

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.

X++ fundamentals: classes, tables, EDTs and enums

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.

Data access: the select family

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.

Set-based operations: the performance imperative

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:

Transaction control: ttsbegin, ttscommit, ttsabort

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:

Extension constructs: the X++ vocabulary for sealed code

```xpp [ExtensionOf(classStr(SalesTableType))] final class SalesTableType_MyExtension { public void validateWrite() { // Pre-processing: additional validation before standard logic SalesTable salesTable = this.salesTable();

Async, batch and long-running logic

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:

What .NET IL compilation means — and does not mean

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#.

The SysExtension pattern: plug-in discovery

Epicor uses BPM directives and Functions to inject behaviour dynamically. D365 provides a similar plug-in pattern through SysExtension:

Knowledge check

Summary

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,…