Building Data Entities
Create data entities in D365 F&O for OData integration and data import/export β backing tables, staging tables, field mapping, composite entities, and aggregate entities. This module covers building the entity; Domain 6 covers using it for data movement.
What is a data entity?
Think of a front desk at a hotel.
Behind the scenes, the hotel has separate systems for rooms, billing, housekeeping, and guest preferences. But when you check in, you deal with one person at the front desk who handles everything β they translate your simple request (βI need a roomβ) into actions across multiple backend systems.
A data entity is that front desk. External systems (and import/export tools) talk to one entity β βCustomerβ or βSales Orderβ β and the entity translates that into reads and writes across multiple underlying tables. The external system never needs to know about CustTable, DirPartyTable, LogisticsPostalAddress, or any of the 15+ tables that make up a βcustomer.β
Entities vs views vs tables
| Feature | Table | View | Data Entity |
|---|---|---|---|
| Read/Write | Full CRUD | Read-only | Full CRUD (with validation logic) |
| OData endpoint | No | No | Yes β automatic REST API |
| Staging table | No | No | Yes β generated for import |
| Business logic | Table methods | Computed columns only | Entity methods (validate, default, transform) |
| Multiple backing tables | No β single table | Yes β via joins | Yes β with field mapping to each table |
| Used in Data Management | No | No | Yes β primary tool for import/export |
| Denormalised | Normalised by design | Can flatten joins | Designed to be flat β one row = one business object |
Creating a data entity in Visual Studio
Step-by-step: the entity wizard
- Right-click project β Add New Item β Data Model β Data Entity
- Primary data source: Select the root table (e.g.,
AxionInspectionTable) - Public entity name: The OData endpoint name (e.g.,
AxionInspections) - Public collection name: Plural form for OData (e.g.,
AxionInspections) - Enable public API: Yes for OData access, No for Data Management only
- Staging table: Auto-generated (e.g.,
AxionInspectionStaging)
Entity properties
| Property | Purpose | Typical Value |
|---|---|---|
| IsPublic | Exposes entity as OData endpoint | Yes for integrations, No for internal DMF only |
| PublicEntityName | OData entity name in the URL | AxionInspections β /data/AxionInspections |
| PublicCollectionName | OData collection name | Same as PublicEntityName (plural) |
| DataManagementEnabled | Appears in Data Management workspace | Yes |
| DataManagementStagingTable | Staging table for imports | Auto-generated |
| PrimaryKey | Natural key fields for the entity | Maps to the tableβs unique key |
| AutoPopulateFields | Whether to auto-add all fields from data sources | Yes (then remove what you donβt need) |
Scenario: Vik builds the Inspections entity
Axion Dynamics needs to import inspection records from their legacy quality system. Vik creates a AxionInspectionEntity with:
- Primary data source:
AxionInspectionTable - Joined data source:
HcmWorker(to resolve inspector name to worker RecId) - Natural key:
InspectionId - IsPublic: Yes (they also need OData for a Power App)
The entity flattens βInspectionId, Description, InspectorName, InspectionDate, Statusβ into one row β the external system doesnβt need to know that the inspector name comes from a different table.
Backing tables and data sources
An entity can have multiple data sources (backing tables) joined together:
Data source properties
| Property | Purpose |
|---|---|
| Is Read Only | Whether this data source supports writes (No = writable) |
| Join type | InnerJoin, OuterJoin β how this table joins to its parent |
| Auto query | Relation-based auto-join or manual field mapping |
Field mapping
Each entity field maps to a specific field on a specific data source:
// Entity: AxionInspectionEntity
// Field mappings:
// InspectionId β AxionInspectionTable.InspectionId
// Description β AxionInspectionTable.Description
// InspectorName β HcmWorker.Name (read-only β resolved from worker table)
// InspectionDate β AxionInspectionTable.InspectionDate
// Status β AxionInspectionTable.Status
//
// InspectorName is mapped to HcmWorker (read-only data source)
// On import, the entity resolves "John Smith" β HcmWorker.RecId β stores RecId
Exam tip: natural key vs surrogate key
Data entities expose natural keys (human-readable values like InspectionId, CustAccount) β not surrogate keys (RecId). External systems donβt know RecId values.
During import, the entity must resolve natural keys to RecIds. For example: the import file contains βJohn Smithβ as the inspector. The entity looks up HcmWorker to find the RecId for John Smith and stores the RecId in the inspection table.
If the lookup fails (name not found), the staging record gets an error status. The exam tests this resolution concept.
Staging tables
Every data entity with DataManagementEnabled = Yes gets a staging table β an intermediary where import data lands before being pushed to the real tables.
Import flow
External File β Parse β Staging Table β Validate β Target Tables
β (errors stay here)
Error log with row-level details
Why staging tables matter
| Benefit | How It Helps |
|---|---|
| Validation | Data is validated before touching production tables |
| Error handling | Failed rows stay in staging with error details β fix and re-import |
| Transformation | Entity methods can transform data between staging and target |
| Performance | Bulk insert into staging, then process in batches |
| Rollback | If import fails midway, production tables are untouched |
Staging table structure
The staging table is auto-generated and mirrors the entityβs fields, plus:
DefinitionGroupβ identifies which import job this row belongs toExecutionIdβ unique ID for this import runIsSelectedβ whether this row should be processedTransferStatusβ Completed, Error, NotStarted
Entity methods (validation and defaulting)
Data entities support X++ methods that run during import/export:
// On AxionInspectionEntity
// Runs before each row is inserted into target tables
public boolean validateWrite()
{
boolean ret = super();
if (!this.InspectionId)
{
ret = checkFailed("Inspection ID is required");
}
if (this.InspectionDate > today())
{
ret = checkFailed("Inspection date cannot be in the future");
}
return ret;
}
// Maps staging fields to target table fields during import
public void mapEntityToDataSource(
DataEntityRuntimeContext _entityCtx,
DataEntityDataSourceRuntimeContext _dataSourceCtx)
{
super(_entityCtx, _dataSourceCtx);
// Custom mapping: resolve inspector name to RecId
if (_dataSourceCtx.name() == dataEntityDataSourceStr(
AxionInspectionEntity, AxionInspectionTable))
{
// Lookup logic handled by entity framework
// when field has proper EDT and relation defined
}
}
// Runs after insert to perform additional business logic
public void postLoad()
{
super();
// Post-processing after data loads from DB (for export scenarios)
}
Composite entities
A composite entity groups multiple related entities into a single unit for import/export. Think: importing a sales order header AND its lines in one file.
When to use composite entities
| Scenario | Why Composite |
|---|---|
| Header + Lines | Sales order header and lines in one import file |
| Master + Details | Customer record + addresses + contacts in one package |
| Parent + Children | Product master + variants + dimensions together |
Structure
// Composite Entity: AxionInspectionCompositeEntity
// Root Entity: AxionInspectionEntity (header)
// Child Entity: AxionInspectionLineEntity (lines)
// Link: InspectionId == InspectionId
Exam tip: composite entity import format
Composite entities require XML format for import β they donβt support CSV because CSV is flat and canβt represent parent-child hierarchy.
The XML structure nests child records inside parent records:
<AxionInspection>
<InspectionId>INS-001</InspectionId>
<Lines>
<Line><LineNum>1</LineNum><Measurement>4.5</Measurement></Line>
<Line><LineNum>2</LineNum><Measurement>3.8</Measurement></Line>
</Lines>
</AxionInspection>If the exam asks about importing header-line data from CSV, youβd need separate entity imports β one for headers, one for lines β not a composite entity.
Aggregate entities
Aggregate entities are read-only entities backed by aggregate measurements (pre-computed OLAP-style data from the Entity Store). Theyβre used for analytical scenarios, not transactional.
| Feature | Regular Entity | Aggregate Entity |
|---|---|---|
| Data source | Tables in AXDB | Aggregate measurements (Entity Store) |
| Read/Write | Read and write | Read-only |
| Use case | Transactional operations, import/export | Analytical dashboards, Power BI |
| Performance | Real-time against OLAP or OLTP | Pre-computed, very fast for aggregation |
Entity development best practices
Vikβs checklist when building entities at Axion:
- Natural keys, not RecIds β external systems use human-readable identifiers
- Mark read-only data sources β joined reference tables should be read-only
- Validate in validateWrite() β catch bad data before it reaches production
- Test staging import β always test with the Data Management workspace, not just OData
- Prefix custom entities β
AxionInspectionEntity, not justInspectionEntity - Set IsPublic intentionally β only expose to OData if external systems need it
- Handle missing lookups β if a natural key canβt be resolved, the row should fail gracefully with a clear error
Scenario: Nikhil's first entity mistake
Junior Nikhil creates an entity that exposes RecId as the primary key. The integration team sends import files with RecId values from their test environment. In production, RecIds are different β every import fails.
Vik explains: βEntities expose natural keys like InspectionId, not RecIds. RecIds are database-specific surrogate keys β theyβre different in every environment. The entity framework resolves natural keys to RecIds automatically.β
Vik creates AxionInspectionEntity with IsPublic = Yes and DataManagementEnabled = Yes. An external Power App needs to read inspection data via REST. Which URL pattern would the Power App use?
During a data import, the staging table shows 200 records with TransferStatus = 'Error'. The error message says 'Inspector not found' for each row. What is the most likely cause?
Axion needs to import inspection headers and their detail lines from a single file. The file is in CSV format. What should Vik do?
Next up: Forms, Menus, and UI Patterns β build user interfaces with form patterns, menu items, and the label system.