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 dataMetadata
EntityEntityMetadata
Attribute valuesAttributeMetadata
Record identifierPrimaryIdAttribute definition
Lookup valuesRelationship definitions
Business recordsTable definitions
RetrieveRequestRetrieveEntityRequest

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 classResponsibility
EntityMetadataTable definition
AttributeMetadataBase column definition
StringAttributeMetadataText column characteristics
IntegerAttributeMetadataInteger column characteristics
DecimalAttributeMetadataDecimal precision and limits
PicklistAttributeMetadataChoice column configuration
LookupAttributeMetadataLookup column characteristics
OneToManyRelationshipMetadataOne-to-many relationship definition
ManyToManyRelationshipMetadataMany-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:

PropertyIllustrative value
Display nameMetadata Snapshot
Schema namepea_MetadataSnapshot
Logical namepea_metadatasnapshot
Entity set nameRetrieved from Dataverse metadata
Primary identifierRetrieved 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,PrimaryNameAttribute
Accept: 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
|
v
Dataverse ServiceClient
|
v
RetrieveEntityRequest
|
v
EntityMetadata / AttributeMetadata
|
v
Metadata Inspection and Serialization
|
v
Dataverse Custom Tables
|
+---- Metadata Snapshots
|
+---- Execution Logs
|
v
Model-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

ColumnTypePurpose
pea_nameSingle line of textPrimary name
pea_tablelogicalnameSingle line of textInspected table
pea_tableschemanameSingle line of textSchema identifier
pea_entitysetnameSingle line of textWeb API entity set
pea_primaryidattributeSingle line of textPrimary identifier column
pea_primarynameattributeSingle line of textPrimary display column
pea_ownershiptypeSingle line of textOwnership metadata
pea_attributecountWhole numberReturned attribute count
pea_capturedonDate and timeSnapshot timestamp
pea_rawmetadataMultiple lines of textSerialized 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

ColumnTypePurpose
pea_nameSingle line of textExecution identifier
pea_operationSingle line of textExecuted operation
pea_targettableSingle line of textTarget table
pea_successYes/NoOperation outcome
pea_durationmsWhole numberClient-measured duration
pea_messageMultiple lines of textResult or sanitized error
pea_executedonDate and timeExecution 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.MetadataExplorer
cd APP001.MetadataExplorer
dotnet 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 itemPurpose
Metadata DashboardOperational overview
Metadata SnapshotsInspect captured schema information
Execution LogsDiagnose 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:

  1. Authenticate.
  2. Retrieve the requested metadata.
  3. Create metadata snapshots.
  4. Create execution logs.
  5. 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
|
v
Create 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.

ApproachAdvantagesEngineering considerations
RetrieveEntityRequestStrongly typed SDK metadata modelCan return more information than needed
Web API EntityDefinitionsREST and OData-based metadata queriesRequires understanding metadata query semantics
RetrieveMetadataChangesRequestSelective metadata retrieval and change detectionMore 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.

TestExpected result
Retrieve account metadataValid table metadata returned
Inspect attribute definitionsColumn metadata available
Inspect an invalid tablePlatform error captured
Create metadata snapshotDataverse record persisted
Create execution logDiagnostic record persisted
Open Metadata DashboardActual captured information displayed
Open Snapshot formRetrieved metadata properties visible
Open Execution Log formOperation details visible
Execute with restricted identityEffective authorization behavior established
Repeat inspection after schema changeNew 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.

  1. Customize table definitions — Dataverse table metadata and programmatic customization.
  2. Query schema definitions — SDK metadata retrieval, filtering, and change detection.
  3. Query table definitions using the Web API — OData metadata queries and entity definitions.
  4. Use the Web API with table definitions — Dataverse metadata operations through REST.
  5. Use connection strings in XRM tooling — Connection configuration and authentication.
  6. Model-driven app navigation — Application navigation architecture.
  7. 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.

Edvaldo Guimrães Filho Avatar

Published by