Skip to main content

Views

A View in the Application Object Tree (AOT) is a virtual table whose content is defined by a query over one or more underlying tables. Views are compiled into SQL Server views during a database synchronisation, and at runtime they behave like read-only table buffers. You can select from a view with the same X++ select syntax used for tables, but you cannot insert, update, or delete records through it.

Views serve two primary purposes:

  1. Encapsulating complex query logic — joins, filters, groupings, and computed columns are defined once in the view and reused everywhere.
  2. Providing a stable read interface — forms, reports, data entities, and other queries consume the view without knowing the underlying join structure. If the underlying tables change, only the view definition needs to be updated.

Once a view is synchronised to the database it produces a standard SQL CREATE VIEW statement. Because the result set is read-only, a view buffer has no insert(), update(), or delete() methods. Think of it as a table record buffer that only supports select operations.


Data Sources

Every view needs at least one data source — the table (or tables) whose columns the view exposes. There are two ways to define data sources:

AOT Query Reference

The Query property on the view can reference a standalone AOT Query object. When set, the view inherits the full query definition — data sources, joins, ranges, and sort orders — from that query. This is useful when the same query logic is shared across multiple consumers (views, forms, reports, data entities).

To use this approach, create (or reuse) a Query object in the AOT, then set the view's Query property to its name. The view's ViewMetadata node is populated automatically from the referenced query.

note

Queries are covered in detail in the Queries section, which provides a full reference on data sources, ranges, relations, and query structure.

Inline Metadata (Manual Data Sources)

If the view does not reference a standalone query, data sources are defined directly inside the view's ViewMetadata node. This embedded query structure is identical to a standalone query — you add data sources, define joins between them, and set ranges and sort orders — but the definition lives inside the view rather than as a separate AOT object.

Use inline metadata when the query logic is specific to this view and not reused elsewhere.

Joins Between Data Sources

When a view contains multiple data sources, they are joined together using the same join modes available on queries:

Join ModeBehaviour
InnerJoinReturns only rows where a match exists in both tables.
OuterJoinReturns all rows from the parent data source; unmatched child rows return null.
ExistsJoinReturns parent rows where at least one child match exists. No child columns are returned.
NotExistsJoinReturns parent rows where no child match exists.

Building Complex Queries with Layered Views

In raw SQL it is common to nest subqueries — a SELECT inside a FROM or WHERE clause — to solve problems that cannot be expressed with flat joins alone. The D365 query framework does not support subqueries directly, which can make certain requirements seem impossible. Views eliminate this limitation.

The technique is straightforward: represent each subquery as its own view, then use those views as data sources in a higher-level view. Because a view is materialised as a SQL Server view, the database engine treats it exactly like a table. A view that joins to another view produces the same execution plan as a query with nested subqueries — but the logic is defined entirely within the AOT.

Why This Matters

Consider a common scenario: a form needs to display a calculated value per row — for example, the total open invoice amount per customer. The typical approach is a display method, but display methods execute per-row in X++ — they cannot be sorted or filtered by the user, and on large data sets they cause performance problems.

With layered views, the aggregated total becomes a real SQL column that users can sort, filter, and export. See Code Examples for the full pattern.

Multi-Level Nesting

There is no practical limit to the depth of nesting. A view can reference other views, which in turn reference further views. Each layer adds a subquery in the generated SQL. For example:

FinalView
├── data source: SummaryView (aggregation layer)
│ ├── data source: FilteredView (pre-filtered subset)
│ │ └── data source: BaseTable
│ └── data source: LookupTable
└── data source: AnotherTable

This pattern lets you decompose problems that would require correlated subqueries, HAVING clauses, window functions (via computed columns), or multiple aggregation levels — all within the AOT and without writing raw SQL.

tip

When replacing a display method with a layered view, the form data source changes from a table to a view. Because a view buffer is read-only, ensure the form is either a read-only list page or that editable fields are handled through a separate data source joined to the view.

Layered Views Composition — building complex queries with composable SQL views


Bound Fields vs. Computed Fields

Every field on a view is either bound or computed. Understanding the distinction is essential.

Bound Fields

A bound field maps directly to a column in one of the view's data sources. You specify the DataSource (which data source) and DataField (which column on that data source). The value is pulled straight from the underlying table with no transformation.

Bound fields can optionally apply an aggregation function (Sum, Count, Min, Max, Avg) when the view uses grouping. When no aggregation is set, the field passes through the raw value.

Computed Fields

A computed field does not map to a single data source column. Instead, its value is defined by a static method that returns a SQL expression as a string. The string returned by the method becomes the literal SQL that the database engine executes inside the CREATE VIEW statement.

The method must be a server static method that returns str. It receives no runtime data — it constructs a SQL fragment at compile/sync time. The SysComputedColumn class provides a library of helper methods for building these SQL fragments safely.

How It Works

  1. Create a static method on the view (or a helper class) that returns str.
  2. Set the computed field's ViewMethod property to the method name (if on the view) or Method property to ClassName::MethodName (if external).
  3. During database synchronisation, the framework calls the method, takes the returned string, and splices it into the CREATE VIEW SQL as the column expression.

The SysComputedColumn class provides helper methods for building SQL fragments safely (if, concat, cast, isNullValue, aggregation functions, etc.). See Code Examples for implementation patterns and the full helper method reference.

warning

The string returned by a computed column method is injected directly into the SQL view definition. Always use SysComputedColumn helpers and DictView.computedColumnString() to generate column references — never hard-code physical SQL column names.

tip

Computed columns are evaluated at sync time, not at query time. The method runs once during database synchronisation. You cannot use runtime variables, user context, or session state in computed column methods.


Using a View in X++

A view buffer behaves like a read-only table — use standard X++ select syntax including while select, joins, and aggregation. See Code Examples for usage patterns.

danger

Do not call insert(), update(), or delete() on a view buffer. Views are read-only. The compiler will not prevent the call (inherited from Common), but the runtime will throw an error.


Properties

31/31 properties
PropertyDisplay NameTypeDescription
Inherited from AxDataEntity
NameNameStringThe name of the element.
LabelLabelStringLabel containing a user-friendly name of the view.
SingularLabelSingular LabelStringLabel used to describe one row in the view.
DeveloperDocumentationDeveloper DocumentationStringText explaining the view to developers.
FormRefForm RefStringMenu item identifying the form to use when the view is referenced in "Go to table" or "View Details" operations.
ListPageRefList Page RefStringMenu item designating the form to use to show lists of records from this view.
ReportRefReport RefStringMenu item identifying the report to use when the view is referenced.
PreviewPartRefPreview Part RefStringMenu item designating the form to use to display previews of records from this view.
TitleField1Title Field1StringValues of this field are displayed in form titles to identify the current record.
TitleField2Title Field2StringValues of this field are displayed in form titles to identify the current record (secondary).
ConfigurationKeyConfiguration KeyStringThe configuration key assigned to the item.
VisibleVisibleNoYesDetermines whether controls bound to the view will be visible on a form. Values: No (0), Yes (1).
IsObsoleteIs ObsoleteNoYesDetermines whether the element is deprecated. Values: No (0), Yes (1).
TagsTagsStringTags for this element separated by semicolon.
TableGroupTable GroupTableGroupSelect which group the view is part of. Values: Miscellaneous (0), Parameter (1), Group (2), Main (3), Transaction (4), WorksheetHeader (5), WorksheetLine (6), Framework (7), Reference (8), Worksheet (9), TransactionHeader (10), TransactionLine (11), Staging (12).
EntityRelationshipTypeEntity Relationship TypeEntityRelationshipTypeDistinguishes entity views from relationship views. Values: Entity (0), Relationship (1).
VisibilityVisibilityCompilerVisibilityAccess level visibility for the view. Values: Private (0), Protected (1), Public (2), Internal (3), InternalProtected (4).
CountryRegionCodesCountry Region CodesStringComma-separated list of ISO country codes where this view is valid.
CountryRegionContextFieldCountry Region Context FieldStringSpecifies the field used to identify the country context.
OperationalDomainOperational DomainOperationalDomainOperational domain responsible for publishing the data. Values: NotSpecified (0), Shared (1), Company (2), Local (3).
SubscriberAccessLevelSubscriber Access LevelAccessGrantMaximum access level granted to subscribers of the operational domain.
View-specific
QueryQueryStringAOT Query that defines the view. When set, the view inherits data sources, joins, and ranges from the referenced query.
AosAuthorizationAOS AuthorizationAosAuthorizationAuthorization setting for the view. Values: None (0), CreateDelete (1), UpdateDelete (2), CreateUpdateDelete (3), CreateReadUpdateDelete (4), Read (5).
ValidTimeStateEnabledValid Time State EnabledNoYesWhether the view has valid time state semantics. Values: No (0), Yes (1).
UpdatableUpdatableNoYesWhether the view is updatable. Values: No (0), Yes (1).
IsPublicIs PublicNoYesWhether the view is publicly accessible. Values: No (0), Yes (1).
CollectionNameCollection NameStringCollection name used in OData and data entity contexts.
ReplacementKeyReplacement KeyStringSelect the natural key index for the view.
IsStagedIs StagedNoYesWhether the view is staged. Values: No (0), Yes (1).
VersionVersionStringVersion information for the view.
MessagingRoleMessaging RoleMessagingRoleMessaging role assigned to the view. Values: None (0), Consumer (1), Subscriber (2), Producer (3), Publisher (4).