
Engineering a Dataverse Metadata Explorer with C# and Model-Driven Apps
Power Platform Engineering Academy | Article 001 of 100
Exploring Microsoft Dataverse metadata, SDK architecture, schema discovery, and operational diagnostics through an integrated engineering application.
Introduction
Microsoft Dataverse is much more than a relational data storage service behind Power Apps. It is a metadata-driven application platform in which table definitions, columns, relationships, ownership models, security configurations, and application capabilities are represented through a rich metadata system.
For developers accustomed to C#, SQL Server, REST APIs, and enterprise integration architectures, understanding this metadata system is essential.
A conventional application queries business records. A metadata-aware application investigates the structures that define those records.
This distinction opens the door to advanced engineering capabilities: schema discovery, automated validation, integration contract analysis, environment comparison, application generation, and dependency inspection.
In this first experiment of the Power Platform Engineering Academy, we will design a Dataverse Metadata Explorer using the Dataverse SDK for .NET and a Model-Driven App as its operational interface.
The application will retrieve metadata programmatically, inspect table and column definitions, persist metadata snapshots, and expose diagnostic information through a dedicated dashboard.
The objective is not merely to learn an SDK method. It is to understand how Dataverse exposes its internal schema as a programmable contract.
Engineering status: This article presents a proposed implementation. Code, configuration, and expected results must be validated against a real Dataverse environment during the practical experiment. No successful execution or deployment is claimed here.
1. Understanding Dataverse as a Metadata-Driven Platform
In a traditional relational database, developers frequently discover the schema through database catalogs or information-schema views.
Dataverse provides a different abstraction.
Its metadata layer describes the structures exposed to applications and services while hiding the underlying physical database implementation.
For example, a Dataverse table contains metadata describing:
- Logical and schema names.
- Primary identifier and primary display column.
- Attribute definitions and data types.
- Ownership characteristics.
- Relationships with other tables.
- Supported operations and capabilities.
- Properties affecting application behavior.
This information is available programmatically through the Dataverse SDK and Web API.
1.1 Business data versus metadata
The SDK distinguishes between business records and schema definitions.
| Business data | Metadata |
|---|---|
Entity | EntityMetadata |
| Attribute values | AttributeMetadata |
| Record identifier | PrimaryIdAttribute definition |
| Lookup values | Relationship definitions |
| Business records | Table definitions |
RetrieveRequest | RetrieveEntityRequest |
A business-data query retrieves records stored in a table.
A metadata query retrieves information describing the table itself.
For example:
var account = service.Retrieve( "account", accountId, new ColumnSet("name"));
This retrieves an Account record.
By contrast:
var request = new RetrieveEntityRequest{ LogicalName = "account", EntityFilters = EntityFilters.Entity};
This requests metadata describing the Account table.
These operations address fundamentally different engineering questions.
2. The Dataverse Metadata Object Model
The Dataverse SDK exposes a hierarchy of metadata classes.
At the table level, EntityMetadata represents the definition of a table.
At the column level, AttributeMetadata describes individual attributes.
Specialized attribute types provide additional properties relevant to their data types.
Examples include:
| Metadata class | Responsibility |
|---|---|
EntityMetadata | Table definition |
AttributeMetadata | Base column definition |
StringAttributeMetadata | Text column characteristics |
IntegerAttributeMetadata | Integer column characteristics |
DecimalAttributeMetadata | Decimal precision and limits |
PicklistAttributeMetadata | Choice column configuration |
LookupAttributeMetadata | Lookup column characteristics |
OneToManyRelationshipMetadata | One-to-many relationship definition |
ManyToManyRelationshipMetadata | Many-to-many relationship definition |
This object model is particularly valuable when developing tools that must understand Dataverse structures without relying on manually maintained schema descriptions.
2.1 Logical names, schema names, and entity set names
Consider a custom table called Metadata Snapshot.
Its identifiers might include:
| Property | Illustrative value |
|---|---|
| Display name | Metadata Snapshot |
| Schema name | pea_MetadataSnapshot |
| Logical name | pea_metadatasnapshot |
| Entity set name | Retrieved from Dataverse metadata |
| Primary identifier | Retrieved from Dataverse metadata |
The distinction matters because different interfaces and APIs use different identifiers.
For example, the Dataverse Web API uses entity set names to address record collections.
A developer should not assume that the entity set name can always be derived by pluralizing the table’s logical name.
Instead, the integration should retrieve EntitySetName from metadata.
A corresponding Web API query is:
GET https://<organization>.crm.dynamics.com/api/data/v9.2/EntityDefinitions(LogicalName='account')?$select=LogicalName,SchemaName,EntitySetName,PrimaryIdAttribute,PrimaryNameAttributeAccept: application/json
This allows the integration to discover the identifiers provided by Dataverse rather than inventing naming conventions.
3. Application Architecture
Our application consists of three main layers.
Metadata acquisition layer
An independent C# console application connects to Dataverse through ServiceClient and executes SDK metadata requests.
Persistence layer
Custom Dataverse tables store metadata snapshots and execution diagnostics.
Presentation layer
A Model-Driven App provides a dashboard, forms, views, and navigation for inspecting captured information.
The proposed execution flow is:
C# Console Application | vDataverse ServiceClient | vRetrieveEntityRequest | vEntityMetadata / AttributeMetadata | vMetadata Inspection and Serialization | vDataverse Custom Tables | +---- Metadata Snapshots | +---- Execution Logs | vModel-Driven App | +---- Entry Dashboard +---- Snapshot Views +---- Snapshot Forms +---- Diagnostic Views
This separation is deliberate.
The console application performs SDK operations. Dataverse stores the inspection results. The Model-Driven App provides the operational interface.
The Model-Driven App is not a substitute for the SDK component; it makes the SDK experiment observable and usable.
4. Designing the Dataverse Data Model
We will use the academy publisher prefix pea and the solution PEA.Dataverse.
The first application introduces two dedicated tables.
4.1 Metadata Snapshot table
Display name: Metadata Snapshot
Logical name: pea_metadatasnapshot
| Column | Type | Purpose |
|---|---|---|
pea_name | Single line of text | Primary name |
pea_tablelogicalname | Single line of text | Inspected table |
pea_tableschemaname | Single line of text | Schema identifier |
pea_entitysetname | Single line of text | Web API entity set |
pea_primaryidattribute | Single line of text | Primary identifier column |
pea_primarynameattribute | Single line of text | Primary display column |
pea_ownershiptype | Single line of text | Ownership metadata |
pea_attributecount | Whole number | Returned attribute count |
pea_capturedon | Date and time | Snapshot timestamp |
pea_rawmetadata | Multiple lines of text | Serialized metadata summary |
Each record represents a metadata inspection performed at a specific time.
The table is not intended to replace the authoritative metadata definitions. It provides historical inspection evidence.
4.2 Execution Log table
Display name: Execution Log
Logical name: pea_executionlog
| Column | Type | Purpose |
|---|---|---|
pea_name | Single line of text | Execution identifier |
pea_operation | Single line of text | Executed operation |
pea_targettable | Single line of text | Target table |
pea_success | Yes/No | Operation outcome |
pea_durationms | Whole number | Client-measured duration |
pea_message | Multiple lines of text | Result or sanitized error |
pea_executedon | Date and time | Execution timestamp |
This table allows the application to expose operational history, including failures.
A useful engineering application must make failures observable rather than displaying only successful operations.
5. Creating the Independent C# Project
The proposed project is:
PowerPlatformEngineeringAcademy/└── Dataverse/ └── APP001.MetadataExplorer/ ├── APP001.MetadataExplorer.csproj └── Program.cs
Create the project using the .NET CLI:
dotnet new console \ --name APP001.MetadataExplorercd APP001.MetadataExplorerdotnet add package Microsoft.PowerPlatform.Dataverse.Client
Use a currently supported .NET target framework compatible with the selected SDK package.
The application will authenticate, retrieve metadata, inspect attributes, persist a snapshot, and record execution diagnostics.
5.1 Authentication and connection management
The Dataverse SDK provides ServiceClient, which supports authenticated connections to Dataverse.
Credentials should not be embedded directly in the source code.
For this laboratory, we will retrieve a supported Dataverse connection string from an environment variable:
string connectionString = Environment.GetEnvironmentVariable( "DATAVERSE_CONNECTION") ?? throw new InvalidOperationException( "DATAVERSE_CONNECTION is missing.");
The exact connection-string properties depend on the chosen authentication mechanism.
For interactive development, a supported interactive authentication configuration may be appropriate.
For unattended execution, an application identity with appropriately managed credentials is preferable.
Authentication establishes identity; it does not automatically grant permission to read metadata or write custom table records.
6. Complete C# Metadata Explorer
The following implementation demonstrates the central APP-001 experiment.
It assumes the custom Dataverse tables and columns have been created and published.
using System;using System.Diagnostics;using System.Linq;using System.Text.Json;using Microsoft.Crm.Sdk.Messages;using Microsoft.PowerPlatform.Dataverse.Client;using Microsoft.Xrm.Sdk;using Microsoft.Xrm.Sdk.Messages;using Microsoft.Xrm.Sdk.Metadata;const string SnapshotTable = "pea_metadatasnapshot";const string LogTable = "pea_executionlog";string targetTable = args.Length > 0 ? args[0] : "account";string connectionString = Environment.GetEnvironmentVariable( "DATAVERSE_CONNECTION") ?? throw new InvalidOperationException( "Missing DATAVERSE_CONNECTION.");using var client = new ServiceClient(connectionString);if (!client.IsReady){ throw new InvalidOperationException( $"Dataverse connection failed: {client.LastError}");}var identity = (WhoAmIResponse)client.Execute( new WhoAmIRequest());Console.WriteLine($"Connected user: {identity.UserId}");Console.WriteLine($"Target table: {targetTable}");var stopwatch = Stopwatch.StartNew();bool succeeded = false;string resultMessage = string.Empty;int exitCode = 0;try{ var request = new RetrieveEntityRequest { LogicalName = targetTable, EntityFilters = EntityFilters.Entity | EntityFilters.Attributes, RetrieveAsIfPublished = false }; var response = (RetrieveEntityResponse) client.Execute(request); EntityMetadata metadata = response.EntityMetadata; var attributes = metadata.Attributes? .OrderBy(a => a.LogicalName) .ToArray() ?? Array.Empty<AttributeMetadata>(); Console.WriteLine("\nTABLE METADATA"); Console.WriteLine($"Logical name: {metadata.LogicalName}"); Console.WriteLine($"Schema name: {metadata.SchemaName}"); Console.WriteLine($"Entity set: {metadata.EntitySetName}"); Console.WriteLine($"Primary ID: {metadata.PrimaryIdAttribute}"); Console.WriteLine($"Primary name: {metadata.PrimaryNameAttribute}"); Console.WriteLine($"Ownership: {metadata.OwnershipType}"); Console.WriteLine($"Attributes: {attributes.Length}"); Console.WriteLine("\nATTRIBUTE METADATA"); foreach (var attribute in attributes) { Console.WriteLine( $"{attribute.LogicalName,-40} " + $"{attribute.AttributeType,-20} " + $"Required: {attribute.RequiredLevel?.Value}"); } DateTime capturedOn = DateTime.UtcNow; var summary = new { metadata.LogicalName, metadata.SchemaName, metadata.EntitySetName, metadata.PrimaryIdAttribute, metadata.PrimaryNameAttribute, OwnershipType = metadata.OwnershipType?.ToString(), AttributeCount = attributes.Length, CapturedOn = capturedOn }; var snapshot = new Entity(SnapshotTable); snapshot["pea_name"] = $"Metadata-{targetTable}-{capturedOn:yyyyMMddHHmmss}"; snapshot["pea_tablelogicalname"] = metadata.LogicalName ?? ""; snapshot["pea_tableschemaname"] = metadata.SchemaName ?? ""; snapshot["pea_entitysetname"] = metadata.EntitySetName ?? ""; snapshot["pea_primaryidattribute"] = metadata.PrimaryIdAttribute ?? ""; snapshot["pea_primarynameattribute"] = metadata.PrimaryNameAttribute ?? ""; snapshot["pea_ownershiptype"] = metadata.OwnershipType?.ToString() ?? ""; snapshot["pea_attributecount"] = attributes.Length; snapshot["pea_capturedon"] = capturedOn; snapshot["pea_rawmetadata"] = JsonSerializer.Serialize(summary); Guid snapshotId = client.Create(snapshot); succeeded = true; resultMessage = $"Snapshot created: {snapshotId}"; Console.WriteLine(resultMessage);}catch (Exception ex){ exitCode = 1; resultMessage = $"{ex.GetType().Name}: {ex.Message}"; Console.Error.WriteLine(resultMessage);}finally{ stopwatch.Stop(); Console.WriteLine( $"Elapsed: {stopwatch.ElapsedMilliseconds} ms"); try { var log = new Entity(LogTable); log["pea_name"] = $"APP001-{Guid.NewGuid():N}"; log["pea_operation"] = "RetrieveEntity"; log["pea_targettable"] = targetTable; log["pea_success"] = succeeded; log["pea_durationms"] = checked( (int)stopwatch.ElapsedMilliseconds); log["pea_message"] = resultMessage; log["pea_executedon"] = DateTime.UtcNow; client.Create(log); } catch (Exception logException) { exitCode = 1; Console.Error.WriteLine( $"Execution logging failed: " + $"{logException.GetType().Name}"); }}Environment.ExitCode = exitCode;
The program intentionally uses a single Program.cs file.
This allows the entire SDK workflow to be studied without introducing unnecessary abstraction layers.
During the practical phase, the code must be compiled and tested against the selected SDK version and the actual Dataverse table definitions.
7. Understanding RetrieveEntityRequest
The most important operation is:
var request = new RetrieveEntityRequest{ LogicalName = targetTable, EntityFilters = EntityFilters.Entity | EntityFilters.Attributes, RetrieveAsIfPublished = false};
Three properties deserve attention.
LogicalName
Identifies the Dataverse table whose metadata will be retrieved.
For example:
LogicalName = "account"
EntityFilters
Controls which metadata categories are requested.
For this experiment:
EntityFilters.Entity |EntityFilters.Attributes
This retrieves table-level metadata and attribute definitions.
Relationships and privileges are not requested.
RetrieveAsIfPublished
Controls whether unpublished customization definitions are included in the metadata retrieval.
Setting it to false requests published metadata.
This matters when investigating discrepancies between a developer’s unpublished customization work and the schema available to ordinary applications.
Why not retrieve everything?
A metadata response can contain many complex objects.
Retrieving all metadata for all tables may increase network transfer, processing, and memory consumption.
A well-designed metadata client should retrieve only the information necessary for its task.
Later applications can investigate RetrieveMetadataChangesRequest for more selective retrieval and metadata cache synchronization.
8. Building the Model-Driven App
Every application in the Power Platform Engineering Academy must include a functional Model-Driven App and a useful entry dashboard.
APP-001 will use the name PEA Metadata Explorer.
The application will contain three primary navigation areas.
| Navigation item | Purpose |
|---|---|
| Metadata Dashboard | Operational overview |
| Metadata Snapshots | Inspect captured schema information |
| Execution Logs | Diagnose operations and failures |
8.1 Entry dashboard
The dashboard should display meaningful information from the experiment.
Proposed indicators include:
Tables inspected: Number of distinct table logical names represented in snapshots.
Metadata snapshots: Total captured metadata records.
Failed inspections: Execution-log records representing failed operations.
Last capture: Most recent successful snapshot timestamp.
A recent-inspections view should display the latest snapshots, including the inspected table, capture timestamp, and attribute count.
The dashboard must distinguish the number of inspected tables from the total number of tables in the Dataverse environment.
For this first version, a supported Model-Driven App dashboard can combine views and charts. A custom page is an alternative if more advanced calculations or interactions are needed.
8.2 Metadata Snapshot form
The main form should expose:
- Table logical name.
- Schema name.
- Entity set name.
- Primary identifier.
- Primary display column.
- Ownership type.
- Number of returned attributes.
- Capture timestamp.
- Serialized metadata summary.
The form provides a convenient inspection interface without requiring users to execute the C# console application themselves.
8.3 Execution Log form
The diagnostic form should expose:
- Operation.
- Target table.
- Success status.
- Execution timestamp.
- Client-measured duration.
- Sanitized diagnostic message.
This interface is particularly important when performing failure-injection experiments.
The Model-Driven App should display real persisted data, not manually entered demonstration values presented as execution results.
9. Failure Injection and Debugging
A technically meaningful experiment must investigate failures, not just successful execution.
9.1 Invalid table name
Execute the application using a nonexistent table:
dotnet run -- invalid_table_name
Expected behavior:
The metadata request should fail with an appropriate Dataverse error.
The application should capture the failure and attempt to persist an execution log.
9.2 Invalid authentication
Configure an invalid or expired authentication context.
Expected behavior:
The connection should fail before metadata retrieval.
The current sample throws an exception during connection initialization and does not persist that failure to Dataverse. This is an important distinction: failures occurring before a working Dataverse connection cannot be logged through that same connection.
A production-oriented implementation would require an independent logging destination for connection failures.
9.3 Insufficient privileges
Execute the application using an identity with restricted Dataverse permissions.
Investigate whether the identity can:
- Authenticate.
- Retrieve the requested metadata.
- Create metadata snapshots.
- Create execution logs.
- Read the resulting records through the Model-Driven App.
These are distinct authorization questions.
A successful authentication request does not imply authorization for every subsequent operation.
9.4 Snapshot persistence failure
Remove the required Create privilege for the snapshot table.
The metadata request may succeed while the snapshot write fails.
This reveals an important distinction between metadata retrieval and persistence.
The application should report the actual failed operation rather than incorrectly claiming that the metadata request itself failed.
10. Transactions and Partial Failures
The sample performs two separate write operations:
Create Metadata Snapshot | vCreate Execution Log
These writes are not atomic.
A snapshot may be created successfully while the subsequent execution-log creation fails.
Likewise, metadata retrieval may succeed while snapshot persistence fails, resulting in a failure log.
This design is acceptable for an initial diagnostic experiment, provided its limitations are understood.
A stronger implementation should distinguish at least three states:
- Metadata retrieval succeeded or failed.
- Snapshot persistence succeeded or failed.
- Diagnostic logging succeeded or failed.
If atomic persistence becomes a requirement, Dataverse transaction mechanisms must be evaluated. However, transaction design should not automatically combine diagnostic logging with the operation being diagnosed, because rolling back the diagnostic record can eliminate valuable failure evidence.
The academy will investigate transaction boundaries and rollback behavior in APP-008.
11. Security Considerations
Metadata inspection is an administrative and engineering capability that should be governed appropriately.
The application must distinguish authentication from authorization.
A caller may successfully connect to Dataverse but lack permission to create records in the custom snapshot tables.
The Model-Driven App also requires appropriate table privileges for its users.
For the initial laboratory, the custom tables can use organization ownership, with access governed through suitable security roles.
The experiment should determine the minimum privileges necessary for each operation rather than assuming System Administrator access is always required.
The application should also avoid persisting sensitive information in diagnostic messages.
Connection strings, authentication tokens, and other secrets must not appear in logs or serialized metadata summaries.
12. Performance Engineering
Metadata requests have different performance characteristics from ordinary business-data queries.
A record query typically returns selected business columns and records.
A metadata request can return complex structural definitions and nested collections.
The amount of returned information depends on the request and selected metadata filters.
For APP-001, the initial strategy is intentionally conservative:
- Inspect one table per execution.
- Request table and attribute metadata.
- Avoid retrieving relationships and privileges unnecessarily.
- Persist only selected metadata properties.
- Measure client-side elapsed time.
The duration measured by Stopwatch is not a pure Dataverse server execution metric.
It may include network latency, response processing, local serialization, and persistence operations.
The sample’s measurement covers metadata retrieval and snapshot creation, but not initial authentication or the final execution-log write.
This boundary must be documented before comparing measurements.
Future experiments should investigate metadata caching, response size, selective retrieval, and the behavior of repeated requests.
13. SDK Versus Web API
Dataverse provides multiple approaches to metadata inspection.
| Approach | Advantages | Engineering considerations |
|---|---|---|
RetrieveEntityRequest | Strongly typed SDK metadata model | Can return more information than needed |
Web API EntityDefinitions | REST and OData-based metadata queries | Requires understanding metadata query semantics |
RetrieveMetadataChangesRequest | Selective metadata retrieval and change detection | More complex query and cache management |
The SDK is a natural choice for this first experiment because it exposes metadata through .NET classes.
The Web API becomes particularly valuable for cross-platform integrations and applications that do not use the Dataverse SDK.
RetrieveMetadataChangesRequest is relevant when implementing long-running metadata-aware services that need to maintain synchronized schema information.
No approach should be declared universally superior without considering its specific use case.
14. Engineering Validation Matrix
The future implementation will be evaluated against the following criteria.
| Test | Expected result |
|---|---|
Retrieve account metadata | Valid table metadata returned |
| Inspect attribute definitions | Column metadata available |
| Inspect an invalid table | Platform error captured |
| Create metadata snapshot | Dataverse record persisted |
| Create execution log | Diagnostic record persisted |
| Open Metadata Dashboard | Actual captured information displayed |
| Open Snapshot form | Retrieved metadata properties visible |
| Open Execution Log form | Operation details visible |
| Execute with restricted identity | Effective authorization behavior established |
| Repeat inspection after schema change | New published metadata reflected in a later snapshot |
Successful execution must be verified through both the C# application and the Model-Driven App.
The application should not be considered complete merely because the console displays metadata.
15. Architectural Lessons
APP-001 introduces several principles that will recur throughout the academy.
Metadata is a programmable contract. Dataverse exposes schema information through supported APIs rather than requiring direct access to its underlying database.
Names have different responsibilities. Display names, schema names, logical names, and entity set names are not interchangeable.
A successful API operation does not guarantee successful persistence. Retrieval, storage, and logging have separate failure boundaries.
Observability is part of application design. A functional engineering application should expose operations, results, and failures.
Snapshots are historical evidence, not authoritative live metadata. Captured schema information may become stale after customization changes.
Security must be tested at each operation boundary. Authentication, metadata access, record creation, and Model-Driven App access require separate consideration.
These lessons provide a foundation for the academy’s later investigations into relationships, Web API integration, security, transactions, concurrency, and ALM.
16. Official Microsoft Technical References
The following Microsoft Learn resources provide the technical foundation for this article.
- Customize table definitions — Dataverse table metadata and programmatic customization.
- Query schema definitions — SDK metadata retrieval, filtering, and change detection.
- Query table definitions using the Web API — OData metadata queries and entity definitions.
- Use the Web API with table definitions — Dataverse metadata operations through REST.
- Use connection strings in XRM tooling — Connection configuration and authentication.
- Model-driven app navigation — Application navigation architecture.
- Add a custom page to your model-driven app — Extending Model-Driven Apps with custom pages.
These references should be checked again during implementation for SDK version compatibility, supported authentication options, and current platform behavior.
Conclusion
The Dataverse Metadata Explorer establishes the first important foundation of our Power Platform engineering journey: understanding Dataverse as a metadata-driven platform rather than simply a collection of tables.
By combining the Dataverse SDK, C#, metadata inspection, custom persistence tables, and a Model-Driven App, we create an application architecture capable of examining and exposing the platform’s own structural definitions.
More importantly, the experiment introduces the engineering discipline that will guide the remaining applications: understanding API contracts, identifying execution boundaries, making failures observable, and distinguishing documented behavior from measured results.
The next step for APP-001 is practical validation: compile the C# project, configure the Dataverse environment, create the tables and dashboard, execute the metadata requests, and document the actual results.
Only after that validation can the proposed design be considered a verified working application.
Power Platform Engineering Academy — Five Years in Five Days
Article 001/100 | Dataverse Metadata Explorer
Engineering mastery through architecture, code, experimentation, and evidence.
