# X++ Data Structures for C# Developers: Bridging the Gap

## From Web APIs to ERP : A Developers Journey

As someone who just recently started in the Dynamics 365 F&O space after spending many years in the world of .NET APIs and Web development, my first encounter with X++ felt like landing on an alien planet. The syntax looked *almost* familiar -- yes, there were curly braces and semicolons -- but the way data structures worked? That was a whole different story.

“Where is the List equivalent in this language?" I remember asking during my first week. "How is it possible to mix different data types in the same collection without type constraints?

If you're a C# developer who's been thrown into the X++ world (or just starting like me), I understand your confusion. This post is everything I wish someone had told me when I first made the transition, written while the struggles and discoveries are still fresh in my mind.

### Understanding the X++ Data Access Architecture

When working with Dynamics 365 F&O, you're dealing with a layered architecture designed specifically for enterprise resource planning (ERP) systems. Unlike the web APIs I was used to building, X++ code operates within the Application Object Server (AOS), which mediates between the user interface and the database.

Here's the full picture of how it all fits together:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1742913572323/d336c767-d703-4a9c-a2cd-86d360a2ab1a.png align="center")

### Try This Now: Explore Your Environment

If you're new to D365 F&O, take a moment to orient yourself:

1. *Open Visual Studio with your D365 F&O development environment*
    
2. *Navigate to AOT &gt; Jobs*
    
3. *Create a new job called "ExploreDataStructures"*
    
4. *Add this simple code to print out the type of architecture you're working with*
    
5. ```csharp
          static void ExploreDataStructures(Args _args)
          {
              info("I'm running in: " + Microsoft.Dynamics.AX.Metadata.Core.xppObject.getApplicationContext().getEnvironment());
              info("Available to me are various X++ data structures!");
          }
    ```
    
    This simple exercise helps establish where your code will execute and reinforces that you're in a different world than typical C# applications.
    

### X++ Data Structures: The Building Blocks

<details data-node-type="hn-details-summary"><summary>Containers: The Swiss Army Knife</summary><div data-type="detailsContent">In the C# world, you're used to strongly-typed collections. In X++, the container is a flexible data structure that can hold multiple data types at once.</div></details>

Try This Now: Your First X++ Container

X++ container with mixed types container `myContainer = ['String value', 123, true];`

```csharp
static void ContainerExperiment(Args _args)
{
    container myFirstContainer = ['Customer', 1001, true];
    
    // Access elements (notice the 1-based indexing!)
    info(conPeek(myFirstContainer, 1)); // Prints "Customer"
    
    // Add a new element
    myFirstContainer += "New Value";
    (conLen(myFirstContainer)); // Prints 4
    
    // What happens if we try to mix operations?
    // Uncomment to see the error:
    // int result = conPeek(myFirstContainer, 1) + conPeek(myFirstContainer, 2);
}
```

What happened when you ran this? Did you notice how X++ handles the mixed types?

*A Bit More Concrete Scenario*

```csharp
//Simplified Example Treat them as Mental Models,In Prod there are various Other considerations to be made


// Prepares reconciliation parameters for a bank statement import
static void SetupBankReconciliationParameters(Args _args)
{
    // Container holding mixed types for reconciliation settings
    // [AccountID, ToleranceAmount, AutoMatchEnabled, MatchingPeriodDays]
    container reconcParams;
    str bankAccountId = 'MAIN001';
    
    // Create container with different parameter types
    reconcParams = [
        bankAccountId,          // String: Bank account ID
        10.00,                  // Real: Tolerance amount for matching
        true,                   // Boolean: Enable automatic matching
        30                      // Integer: Days to look back for matching
    ];
    
    // Display the parameters
    info("Bank Reconciliation Parameters:");
    info(strFmt("Account: %1", conPeek(reconcParams, 1)));
    info(strFmt("Tolerance Amount: $%1", conPeek(reconcParams, 2)));
    info(strFmt("Auto-Matching: %1", conPeek(reconcParams, 3) ? "Enabled" : "Disabled"));
    info(strFmt("Matching Period: %1 days", conPeek(reconcParams, 4)));
    
    // Pass parameters to the reconciliation process
    if (conPeek(reconcParams, 3)) // If auto-matching is enabled
    {
        info("Starting automatic reconciliation process...");
        runBankReconciliation(reconcParams);
    }
}

/// Simulates running the bank reconciliation process

static void runBankReconciliation(container _params)
{
    // In a real system, this would use the parameters to
    // perform reconciliation between bank statements and ledger
    info(strFmt("Reconciling account %1 with $%2 tolerance for the past %3 days",
        conPeek(_params, 1),
        conPeek(_params, 2),
        conPeek(_params, 4)));
}
```

<details data-node-type="hn-details-summary"><summary>Lists: The Ordered Collection</summary><div data-type="detailsContent">In C#, you'd reach for <code>List&lt;T&gt;</code>. In X++, there's the List class, which maintains items in the order you add them.</div></details>

/X++ List List `myList = new List(Types::String); myList.addEnd("First item"); myList.addEnd("Second item");`

Try This Now: Working with Lists

```csharp
static void ListExperiment(Args _args)
{
    List nameList = new List(Types::String);
    nameList.addEnd("Alice");
    nameList.addEnd("Dyamics");
    nameList.addEnd("FnO");
    
    // Lists have iterators
    ListIterator iterator = new ListIterator(nameList);
    
    while (iterator.more())
    {
        info(iterator.value());
        iterator.next();
    }
}
```

*A Bit More Concrete Scenario*

```csharp
//Simplified Example Treat them as Mental Models,In Prod there are various Other considerations to be made


//Generates a list of bank accounts requiring review based on last activity date
static void FindInactiveBankAccounts(Args _args)
{
    BankAccountTable bankAccount;
    List accountsToReview = new List(Types::Record);
    ListIterator iterator;
    date reviewThreshold = systemDateGet() - 90; // Accounts inactive for 90+ days
    
    // Find accounts with no recent activity
    while select bankAccount
        where bankAccount.LastActivity < reviewThreshold &&
              bankAccount.AccountStatus == BankAccountStatus::Active
    {
        // Add accounts requiring review to our list
        accountsToReview.addEnd(bankAccount);
    }
    
    // Process the list of accounts needing review
    info(strFmt("Found %1 bank accounts requiring review", accountsToReview.elements()));
    
    iterator = new ListIterator(accountsToReview);
    while (iterator.more())
    {
        bankAccount = iterator.value();
        info(strFmt("Account %1 (%2) - Last activity: %3", 
                   bankAccount.BankAccountId,
                   bankAccount.Name,
                   bankAccount.LastActivity));
        iterator.next();
    }
}
```

<details data-node-type="hn-details-summary"><summary>Maps: The Key-Value Store</summary><div data-type="detailsContent">If you're used to <code>Dictionary&lt;K,V&gt;</code> in C#, you'll work with Map in X++</div></details>

X++ Map `Map customerMap = new Map(Types::String, Types::Integer); customerMap.insert("CUST001", 1001); customerMap.insert("CUST002", 1002);`

**Building a Customer Map**

```csharp
static void MapExperiment(Args _args)
{
    // Create a map of customer IDs to credit limits
    Map creditLimitMap = new Map(Types::String, Types::Real);
    
    // Add some customers
    creditLimitMap.insert("CUST001", 5000.00);
    creditLimitMap.insert("CUST002", 10000.00);
    
    // Look up a credit limit
    print("Credit limit for CUST001: " + 
        any2str(creditLimitMap.lookup("CUST001")));
    
    // Update a value
    creditLimitMap.insert("CUST001", 7500.00);
    print("New credit limit for CUST001: " + 
        any2str(creditLimitMap.lookup("CUST001")));
}
```

A Bit More Concrete Scenario

Imagine a Scenario where you want to build a quick in-memory lookup of **BankAccountId → CurrentBalance**, so you can:

* Quickly retrieve balances for processing,
    
* Compare multiple accounts,
    
* Avoid repeated queries to the database.
    
* ```csharp
    //Simplified Example Treat them as Mental Models,In Prod there are various Other considerations to be made
    static void Map_BankAccountsBalances(Args _args)
    {
        Map bankBalanceMap = new Map(Types::String, Types::Real); // BankAccountId → Balance
        BankAccountTable bankAccount;
        BankAccountTrans bankTrans;
        AmountCur balance;
    
        // 1. Loop through bank accounts
        while select bankAccount
        {
            // 2. Calculate balance (simplified logic: sum of transactions)
            balance = 0;
            while select sum(AmountCur) from bankTrans
                where bankTrans.BankAccountID == bankAccount.BankAccountId
            {
                balance = bankTrans.AmountCur;
            }
    
            // 3. Add to Map
            bankBalanceMap.insert(bankAccount.BankAccountId, balance);
        }
    
        // 4. Iterate and display
        MapEnumerator mapEnum = bankBalanceMap.getEnumerator();
        while (mapEnum.moveNext())
        {
            str bankId = mapEnum.currentKey();
            real bal   = mapEnum.currentValue();
            info(strFmt("Bank Account: %1 | Balance: %2", bankId, bal));
        }
    }
    ```
    
* ## Database-Related Structures
    

This is where X++ really differs from C# and web development. Instead of ORM frameworks like Entity Framework, X++ provides specialized structures for database operations:

<details data-node-type="hn-details-summary"><summary>RecordSortedList: For Sorted In-Memory Records</summary><div data-type="detailsContent"><strong>A</strong> specialized X++ object that holds records in memory according to a specified sort order. This is especially useful if you need to programmatically build up a list of records with a defined sort sequence</div></details>

```csharp
// Sorting customers by name
RecordSortedList sortedCustomers = new RecordSortedList(tableNum(CustTable));
sortedCustomers.addSortField(fieldNum(CustTable, Name), SortOrder::Ascending);
```

A Bit More Concrete Scenario

```csharp
//// Inserts multiple bank accounts in bulk for better performance
// A Mental Model for the data structure - 
//In Production code you would want to Optimize for 
    //1.  batch processing to handle larger datasets for timeout handing 
    //2. Err Handling  
    //etc

public static void insertCustomersBulk()
{
    RecordInsertList recordInsertList;
    CustTable        custTable;
    int              i;

    // 1. Create a new RecordInsertList for CustTable
    recordInsertList = new RecordInsertList(tableNum(CustTable));

    // 2. Loop and create multiple new records
    for (i = 1; i <= 5; i++)
    {
        custTable.clear();
        custTable.AccountNum = strFmt("NEWACC%1", i);
        custTable.Name       = strFmt("New Customer %1", i);
        // ... assign other fields as necessary

        // 3. Add the newly populated record to the RecordInsertList
        recordInsertList.add(custTable);
    }

    // 4. Commit (bulk insert) all records to the database
    ttsbegin;
    recordInsertList.insertDatabase();
    ttscommit;
}
```

<details data-node-type="hn-details-summary"><summary>RecordSortedList: For Sorted In-Memory Records</summary><div data-type="detailsContent"><strong>A</strong> specialized X++ object that holds records in memory according to a specified sort order. This is especially useful if you need to programmatically build up a list of records with a defined sort sequence</div></details>

Sorting customers by name  
`RecordSortedList sortedCustomers = new RecordSortedList(tableNum(CustTable)); sortedCustomers.addSortField(fieldNum(CustTable, Name), SortOrder::Ascending);`

*A Bit More Concrete Scenario :* In-Memory Record Sorting

```csharp
//Simplified Example Treat them as Mental Models,In Prod there are various Other considerations to be made

//Using RecordSortedList with BankAccountTable
static void InMemorySortingExample(Args _args)
{
    // Create a sorted list of bank accounts by name
    RecordSortedList sortedBankAccounts = new RecordSortedList(tableNum(BankAccountTable));
    sortedBankAccounts.addSortField(fieldNum(BankAccountTable, Name), SortOrder::Ascending);
    
    BankAccountTable bankAccount;
    int count = 0;
    
    while select bankAccount
    {
        if (count++ >= 10)
            break;
            
        sortedBankAccounts.add(bankAccount);
    }
    
    // Now iterate through the sorted bank accounts
    BankAccountTable sortedAcct;
    info("Bank accounts sorted by name:");
    
    for (int i = 1; i <= sortedBankAccounts.elements(); i++)
    {
        sortedAcct = sortedBankAccounts.getRecord(i);
        info(strFmt("%1. %2", i, sortedAcct.Name));
    }
}
```

**Points to Ponder**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1742912722422/65a74bd8-3fcd-4029-a457-bc05cc2d10d2.png align="center")

### X++ vs. C# Side-by-Side: Visual Comparison

To help you visualize the differences, here's a side-by-side comparison of common operations:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1742954184875/677116a1-af04-4e43-9b8d-4a88811ed4b8.png align="center")

### Common Pitfalls When Transitioning from C# to X++

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1742913133261/f3854013-e07a-4592-83d2-b21cb52423c5.png align="center")

## Final Thoughts

As I continue learning my way through the X++ landscape, I'm discovering new patterns and practices every day. This post captures my understanding at this point in my journey, and I'll be sharing new insights as I grow more comfortable with Dynamics 365 F&O development.

If you're also new to this world, I'd love to hear about your experiences and discoveries in the comments. And if you're a seasoned X++ developer who spotted something I've misunderstood, I welcome your gentle corrections -- we're all learning together!

Join me next week when I dive deeper into containers and how they compare to the C# collections I used to rely on. Until then, happy coding in whatever syntax life throws your way!

✨Happy `Coding!`

---
