Exploring Microsoft Dataverse Metadata with C#: Building a Metadata Explorer and Model-Driven App
Power Platform Engineering Academy — APP-001

Introduction
One of the most important differences between Microsoft Dataverse and a conventional relational database is the role metadata plays in the application architecture.
In SQL Server, developers generally interact with schemas, tables, columns, constraints, indexes, and system catalog views. In SharePoint, developers work with lists, content types, fields, field definitions, and REST or Microsoft Graph interfaces.
Dataverse introduces a richer metadata model that describes not only the structure of data but also important aspects of how that data participates in the Power Platform ecosystem.
Tables have logical names, schema names, ownership models, primary attributes, relationships, and security-related metadata. Columns expose specialized types and additional behavioral characteristics. Applications, APIs, forms, and other platform components depend on these definitions.
Understanding this metadata model is essential for developers who want to move beyond configuring Power Apps through the graphical interface and begin engineering sophisticated Dataverse solutions.
In this article, we will design and implement a Dataverse Metadata Explorer using C#, the Microsoft Dataverse SDK, and a Model-Driven App.
The application will retrieve table and column definitions from Dataverse, transform selected metadata into a structured snapshot, persist that snapshot in a custom Dataverse table, and expose the results through an operational dashboard.
Along the way, we will examine the underlying API contracts, authentication boundaries, metadata retrieval strategies, performance considerations, and architectural implications.
This is the first experiment in the Power Platform Engineering Academy, an engineering laboratory focused on developing deep technical proficiency through small, interconnected applications.
Implementation note: This article describes a reproducible implementation. Environment-specific execution results and screenshots will be added after validation.
1. Understanding the Dataverse Metadata Architecture
Dataverse is frequently introduced as the data platform behind Power Apps and Dynamics 365. Although this description is correct, it does not fully explain its architecture.
Dataverse is a metadata-driven platform.
A Dataverse table is defined by metadata objects that describe its structure and characteristics. These definitions are consumed by different platform services and development tools.
For example, consider the standard account table.
Its metadata contains information such as:
- Logical name:
account - Schema name:
Account - Entity set name:
accounts - Primary identifier:
accountid - Primary name attribute:
name - Ownership model
- Attribute definitions
- Relationship definitions
These properties serve different purposes.
The logical name is the stable identifier commonly used when interacting with Dataverse through SDK operations and queries.
The schema name is associated with the table’s definition and customization model.
The entity set name identifies the collection resource used by the Dataverse Web API.
The primary identifier identifies the table’s record ID attribute.
These names are related, but they are not interchangeable.
For instance, a Dataverse Web API request for Account records uses the entity set:
GET /api/data/v9.2/accounts
An SDK metadata request instead refers to the table using its logical name:
LogicalName = "account"
This distinction becomes particularly important when building generic integration frameworks, metadata discovery tools, code generators, and schema comparison utilities.
1.1 Metadata versus data
In the Dataverse SDK, two important concepts must remain separate.
EntityMetadata represents the definition of a table.
Entity represents a data record.
For example:
EntityMetadata tableDefinition;
describes the table itself, while:
Entity account = new Entity("account");
creates an SDK object representing an Account record.
The first belongs to the metadata model. The second belongs to the data model.
This separation is central to the architecture of our Metadata Explorer.
1.2 Dataverse metadata objects
The SDK exposes several metadata classes.
| Metadata class | Purpose |
|---|---|
EntityMetadata | Table definition |
AttributeMetadata | Column definition |
StringAttributeMetadata | Text column metadata |
PicklistAttributeMetadata | Choice column metadata |
LookupAttributeMetadata | Lookup column metadata |
OneToManyRelationshipMetadata | One-to-many relationship definition |
ManyToManyRelationshipMetadata | Many-to-many relationship definition |
Many attribute types extend AttributeMetadata with additional properties.
For example, a text attribute can expose maximum length information, while a choice attribute can expose information about its options.
Consequently, generic metadata inspection requires understanding both the common metadata properties and the specialized metadata classes.
Microsoft Learn documents the metadata model and the supported operations for retrieving table definitions:
2. Designing the Metadata Explorer Architecture
Our experiment will contain two principal applications working against the same Dataverse environment.
The first is a C# Console App responsible for retrieving metadata and persisting snapshots.
The second is a Model-Driven App responsible for displaying and inspecting those snapshots.
The execution architecture is:
Visual Studio | +-- APP001.MetadataExplorer | +-- Program.cs | +-- ServiceClient | +-- Dataverse Organization Service | +-- RetrieveAllEntitiesRequest | +-- RetrieveEntityRequest | +-- EntityMetadata | +-- AttributeMetadata | +-- Metadata Transformation | +-- Create Snapshot Record | v Dataverse pea_metadatasnapshot | v Model-Driven App APP001 Metadata Explorer | +-- Entry Dashboard +-- Metadata Snapshot Views +-- Metadata Snapshot Forms
This design deliberately separates three responsibilities.
Metadata acquisition belongs to the C# application. It interacts directly with Dataverse metadata APIs.
Metadata persistence belongs to a custom Dataverse table. It stores a selected representation of the retrieved definitions.
Metadata presentation belongs to the Model-Driven App. It provides a usable operational interface for inspecting the results.
Why not retrieve metadata directly from the Model-Driven App?
Because the primary objective of this experiment is to explore the Dataverse SDK and its metadata contracts. Persisting snapshots also gives us a history of inspections, allows comparisons between executions, and creates an observable interface for testing.
A more sophisticated implementation could use a Custom API, plugin, or Azure Function to expose live metadata retrieval through the application. Those approaches introduce additional execution and security considerations and are intentionally outside the initial scope.
3. Creating the Visual Studio Project
We will organize the academy inside a Visual Studio solution named:
PowerPlatformEngineeringAcademy
Each experiment will have an independent C# project.
For APP-001, create a Console App with the following configuration:
| Property | Value |
|---|---|
| Solution | PowerPlatformEngineeringAcademy |
| Solution folder | Dataverse |
| Project | APP001.MetadataExplorer |
| Framework | .NET 8 |
| Namespace | PowerPlatformAcademy |
The project structure will initially be simple:
PowerPlatformEngineeringAcademy|+-- Dataverse | +-- APP001.MetadataExplorer | +-- Dependencies +-- Program.cs +-- APP001.MetadataExplorer.csproj
Although a production system would typically distribute responsibilities across services and classes, we will intentionally keep the experimental code in Program.cs.
This allows us to inspect the complete execution path without introducing abstractions that obscure the SDK behavior.
3.1 Installing the Dataverse SDK
Install the NuGet package:
Microsoft.PowerPlatform.Dataverse.Client
Using the .NET CLI:
dotnet add package Microsoft.PowerPlatform.Dataverse.Client
The package provides ServiceClient, the primary connection class used in this experiment.
The SDK exposes operations through the Dataverse organization service interface, including the execution of specialized request messages.
4. Authentication and Connection Management
Dataverse uses Microsoft Entra ID for authentication.
A C# application must acquire an appropriate identity and establish an authenticated connection before executing SDK operations.
For this experiment, we will use interactive OAuth authentication with an explicitly configured Microsoft Entra application registration.
The registration must be configured for the appropriate public-client authentication scenario, including a valid redirect URI and required consent.
We will store the environment-specific configuration in process environment variables:
DATAVERSE_URLDATAVERSE_CLIENT_IDDATAVERSE_REDIRECT_URI
A development environment URL might resemble:
https://yourorganization.crm.dynamics.com
The application ID and redirect URI must correspond to the Microsoft Entra registration used for the experiment.
4.1 Reading the configuration
string environmentUrl = Environment.GetEnvironmentVariable("DATAVERSE_URL") ?? throw new InvalidOperationException( "DATAVERSE_URL is missing.");string clientId = Environment.GetEnvironmentVariable("DATAVERSE_CLIENT_ID") ?? throw new InvalidOperationException( "DATAVERSE_CLIENT_ID is missing.");string redirectUri = Environment.GetEnvironmentVariable("DATAVERSE_REDIRECT_URI") ?? throw new InvalidOperationException( "DATAVERSE_REDIRECT_URI is missing.");
This provides a basic configuration boundary.
Missing configuration is detected before any network operation.
In production systems, configuration management might involve Azure App Configuration, managed identities, or other supported identity and secret-management approaches.
4.2 Establishing the connection
string connectionString = $"AuthType=OAuth;" + $"Url={environmentUrl};" + $"AppId={clientId};" + $"RedirectUri={redirectUri};" + $"LoginPrompt=Auto";using var client = new ServiceClient(connectionString);if (!client.IsReady){ throw new InvalidOperationException( client.LastError ?? "Dataverse connection failed.");}
The IsReady property allows us to determine whether the client is ready for SDK operations.
An important distinction must be made here.
Authentication proves identity. Authorization determines which operations that identity may perform.
A successful connection does not imply that the user can create tables, modify security roles, or access every Dataverse record.
The application must be tested using an identity with the appropriate privileges.
Official reference:
5. Retrieving Dataverse Table Metadata
The Dataverse SDK provides dedicated messages for metadata operations.
Two of the most useful are:
RetrieveAllEntitiesRequestRetrieveEntityRequest
The first retrieves metadata for multiple tables.
The second retrieves metadata for a specific table.
5.1 RetrieveAllEntitiesRequest
The following request retrieves table-level metadata:
var request = new RetrieveAllEntitiesRequest{ EntityFilters = EntityFilters.Entity, RetrieveAsIfPublished = false};var response = (RetrieveAllEntitiesResponse)client.Execute(request);EntityMetadata[] tables = response.EntityMetadata;
The EntityFilters property controls which categories of metadata are requested.
In this example:
EntityFilters.Entity
requests table-level metadata.
The request does not retrieve every attribute and relationship definition.
This matters because metadata responses can become substantial in highly customized environments.
The RetrieveAsIfPublished property controls whether the request includes unpublished customization state.
For this experiment, we use the published metadata view.
5.2 Inspecting returned definitions
Once the response has been received, we can enumerate its tables:
foreach (var table in tables.OrderBy(t => t.LogicalName)){ Console.WriteLine( $"{table.LogicalName} | " + $"{table.SchemaName} | " + $"{table.EntitySetName}");}
This produces an inventory of table definitions.
The actual inventory depends on the Dataverse environment and installed solutions.
It may contain standard Microsoft tables, custom tables, and tables introduced by installed applications.
5.3 RetrieveEntityRequest
To inspect one table in detail, use RetrieveEntityRequest.
var request = new RetrieveEntityRequest{ LogicalName = "account", EntityFilters = EntityFilters.Entity | EntityFilters.Attributes, RetrieveAsIfPublished = false};var response = (RetrieveEntityResponse)client.Execute(request);EntityMetadata metadata = response.EntityMetadata;
The request includes EntityFilters.Attributes, instructing Dataverse to return attribute metadata.
We can then inspect individual columns:
foreach (var attribute in metadata.Attributes){ Console.WriteLine( $"{attribute.LogicalName} | " + $"{attribute.SchemaName} | " + $"{attribute.AttributeType}");}
At this point, we are examining the structure of the account table rather than its business records.
5.4 Why targeted metadata retrieval matters
Metadata inspection tools often begin with broad discovery requests.
However, repeatedly retrieving every table and every attribute is not necessarily efficient.
The request scope affects the volume of metadata transferred and processed.
For production-grade metadata synchronization, Dataverse also supports metadata change queries through RetrieveMetadataChangesRequest.
This allows applications to design more selective metadata retrieval strategies.
Official reference:
6. Persisting Metadata Snapshots in Dataverse
Retrieving metadata through the SDK is useful, but our application must also expose the results through a Model-Driven App.
We therefore need a custom table to store selected metadata properties.
Inside Power Apps, create an unmanaged solution named:
PEA Dataverse
Use the publisher prefix:
pea
Then create the table:
Metadata Snapshot
Its logical name will be:
pea_metadatasnapshot
Configure it as an organization-owned table.
6.1 Table schema
| Column | Logical name | Type |
|---|---|---|
| Name | pea_name | Primary name |
| Table Logical Name | pea_tablelogicalname | Text |
| Schema Name | pea_schemaname | Text |
| Entity Set Name | pea_entitysetname | Text |
| Primary ID Attribute | pea_primaryidattribute | Text |
| Ownership Type | pea_ownershiptype | Text |
| Attribute Count | pea_attributecount | Whole number |
| Inspection Date | pea_inspectiondate | Date and time |
| Metadata JSON | pea_metadatajson | Multiple lines of text |
Verify the generated logical names in the table designer before implementing the persistence code.
The Metadata JSON column should be configured with sufficient capacity for the selected payload.
This design is intentionally compact.
We are not creating one Dataverse record per metadata attribute. Instead, we store table-level information and a serialized representation of its columns.
A normalized metadata registry would require additional tables and relationships, which can be explored in a subsequent experiment.
6.2 Transforming metadata into JSON
We will serialize a selected subset of the retrieved metadata.
var snapshotData = new{ metadata.LogicalName, metadata.SchemaName, metadata.EntitySetName, metadata.PrimaryIdAttribute, OwnershipType = metadata.OwnershipType?.ToString(), Attributes = metadata.Attributes.Select(a => new { a.LogicalName, a.SchemaName, Type = a.AttributeType?.ToString() }).ToArray()};string json = JsonSerializer.Serialize( snapshotData, new JsonSerializerOptions { WriteIndented = true });
The resulting JSON is a diagnostic representation, not a complete serialization of EntityMetadata.
This is an important architectural choice.
Metadata objects may contain numerous properties and nested structures. Attempting to serialize the entire SDK object graph is unnecessary for this experiment and can introduce complexity.
Selecting the properties we need creates a simpler, more stable inspection contract.
6.3 Creating the snapshot record
var snapshot = new Entity("pea_metadatasnapshot");snapshot["pea_name"] = $"Metadata: {metadata.LogicalName}";snapshot["pea_tablelogicalname"] = metadata.LogicalName;snapshot["pea_schemaname"] = metadata.SchemaName;snapshot["pea_entitysetname"] = metadata.EntitySetName ?? string.Empty;snapshot["pea_primaryidattribute"] = metadata.PrimaryIdAttribute ?? string.Empty;snapshot["pea_ownershiptype"] = metadata.OwnershipType?.ToString() ?? string.Empty;snapshot["pea_attributecount"] = metadata.Attributes.Length;snapshot["pea_inspectiondate"] = DateTime.UtcNow;snapshot["pea_metadatajson"] = json;Guid snapshotId = client.Create(snapshot);
The SDK Create operation persists the record in Dataverse and returns its identifier.
The snapshot can then be retrieved using the SDK or inspected through the Model-Driven App.
6.4 Verifying persistence
Entity persisted = client.Retrieve( "pea_metadatasnapshot", snapshotId, new ColumnSet( "pea_name", "pea_tablelogicalname", "pea_attributecount"));Console.WriteLine( persisted.GetAttributeValue<string>( "pea_tablelogicalname"));
Reading the record back provides stronger evidence than relying solely on the success of Create.
It does not validate every stored field, but it confirms that the created record can be retrieved using the returned identifier.
7. Complete C# Implementation
The following Program.cs consolidates the metadata retrieval and persistence operations.
The code assumes the Dataverse table and columns have already been created and published.
using System;using System.Linq;using System.Text.Json;using Microsoft.PowerPlatform.Dataverse.Client;using Microsoft.Xrm.Sdk;using Microsoft.Xrm.Sdk.Messages;using Microsoft.Xrm.Sdk.Metadata;using Microsoft.Xrm.Sdk.Query;namespace PowerPlatformAcademy;internal static class Program{ private static int Main() { try { string url = ReadRequired("DATAVERSE_URL"); string clientId = ReadRequired("DATAVERSE_CLIENT_ID"); string redirectUri = ReadRequired("DATAVERSE_REDIRECT_URI"); string connectionString = $"AuthType=OAuth;" + $"Url={url};" + $"AppId={clientId};" + $"RedirectUri={redirectUri};" + $"LoginPrompt=Auto"; using var client = new ServiceClient(connectionString); if (!client.IsReady) { throw new InvalidOperationException( client.LastError ?? "Connection failed."); } Console.WriteLine("Connected to Dataverse."); // Retrieve table-level metadata. var allRequest = new RetrieveAllEntitiesRequest { EntityFilters = EntityFilters.Entity, RetrieveAsIfPublished = false }; var allResponse = (RetrieveAllEntitiesResponse) client.Execute(allRequest); var tables = allResponse.EntityMetadata .OrderBy(t => t.LogicalName) .ToArray(); Console.WriteLine( $"Retrieved {tables.Length} table definitions."); foreach (var table in tables.Take(20)) { Console.WriteLine( $"{table.LogicalName} | {table.SchemaName}"); } // Retrieve detailed metadata for Account. const string targetTable = "account"; var detailRequest = new RetrieveEntityRequest { LogicalName = targetTable, EntityFilters = EntityFilters.Entity | EntityFilters.Attributes, RetrieveAsIfPublished = false }; var detailResponse = (RetrieveEntityResponse) client.Execute(detailRequest); EntityMetadata metadata = detailResponse.EntityMetadata; AttributeMetadata[] attributes = metadata.Attributes ?? Array.Empty<AttributeMetadata>(); Console.WriteLine( $"Table: {metadata.LogicalName}"); Console.WriteLine( $"Attributes: {attributes.Length}"); // Transform metadata into a compact snapshot. var snapshotData = new { metadata.LogicalName, metadata.SchemaName, metadata.EntitySetName, metadata.PrimaryIdAttribute, OwnershipType = metadata.OwnershipType?.ToString(), Attributes = attributes.Select(a => new { a.LogicalName, a.SchemaName, Type = a.AttributeType?.ToString() }).ToArray() }; string json = JsonSerializer.Serialize( snapshotData, new JsonSerializerOptions { WriteIndented = true }); // Persist the snapshot. var snapshot = new Entity("pea_metadatasnapshot"); snapshot["pea_name"] = $"Metadata: {metadata.LogicalName}"; snapshot["pea_tablelogicalname"] = metadata.LogicalName; snapshot["pea_schemaname"] = metadata.SchemaName; snapshot["pea_entitysetname"] = metadata.EntitySetName ?? string.Empty; snapshot["pea_primaryidattribute"] = metadata.PrimaryIdAttribute ?? string.Empty; snapshot["pea_ownershiptype"] = metadata.OwnershipType?.ToString() ?? string.Empty; snapshot["pea_attributecount"] = attributes.Length; snapshot["pea_inspectiondate"] = DateTime.UtcNow; snapshot["pea_metadatajson"] = json; Guid snapshotId = client.Create(snapshot); Console.WriteLine( $"Snapshot created: {snapshotId}"); // Verify persistence. Entity persisted = client.Retrieve( "pea_metadatasnapshot", snapshotId, new ColumnSet( "pea_name", "pea_tablelogicalname", "pea_attributecount")); Console.WriteLine( "Verified: " + persisted.GetAttributeValue<string>( "pea_tablelogicalname")); Console.WriteLine("Experiment finished."); return 0; } catch (Exception ex) { Console.Error.WriteLine( $"Experiment failed: {ex.GetType().Name}"); Console.Error.WriteLine(ex.Message); return 1; } } private static string ReadRequired(string name) { string? value = Environment.GetEnvironmentVariable(name); if (string.IsNullOrWhiteSpace(value)) { throw new InvalidOperationException( $"{name} is missing."); } return value; }}
This implementation intentionally keeps the workflow visible.
Authentication, metadata retrieval, transformation, persistence, and verification are all contained in one file.
For this laboratory, clarity of execution is more important than introducing a complex class hierarchy.
The implementation is a candidate for compilation and runtime validation; it is not yet a verified execution artifact.
8. Building the Model-Driven App
The C# application is responsible for producing the metadata snapshots.
The Model-Driven App will provide the operational interface for exploring them.
Inside the PEA Dataverse solution, create a Model-Driven App named:
APP001 Metadata Explorer
Add the Metadata Snapshot table to the application.
8.1 Configuring the form
Create a Main form containing the following sections.
Identification
- Name
- Table Logical Name
- Schema Name
Table Definition
- Entity Set Name
- Primary ID Attribute
- Ownership Type
Inspection Information
- Attribute Count
- Inspection Date
Serialized Metadata
- Metadata JSON
The form should allow users to inspect the complete snapshot without returning to Visual Studio.
8.2 Configuring views
Create the following views:
| View | Purpose |
|---|---|
| All Metadata Snapshots | Displays all inspections |
| Recent Inspections | Orders records by inspection date |
| Account Metadata | Filters inspections of the Account table |
These views provide different perspectives on the same data.
They also demonstrate how a Model-Driven App can serve as an operational interface for a C# integration experiment.
8.3 Why a Model-Driven App?
A Console App is excellent for inspecting SDK execution, but it is not an ideal interface for browsing persisted records.
A Model-Driven App provides native Dataverse integration, security enforcement, forms, views, and navigation.
It allows us to observe the results of our integration through a separate application boundary.
This is a recurring architectural pattern in enterprise systems: background or external components perform specialized operations, while an operational application exposes their state and results.
9. Creating the Entry Dashboard
Every application in our engineering academy must provide a useful landing experience.
For APP-001, we will create a Custom page named:
APP001 Metadata Dashboard
A Custom page allows us to combine Power Fx, canvas-style controls, and Dataverse data within a Model-Driven App.
Unlike a standalone Canvas App, the Custom page participates in the Model-Driven App experience.
Microsoft documents the supported creation, navigation, and publishing procedures:
9.1 Dashboard requirements
The dashboard will display:
- Total metadata snapshots
- Most recent inspection
- Number of distinct inspected tables
- Recent inspection records
- Navigation to snapshot details
This information makes the dashboard operational rather than decorative.
9.2 Retrieving snapshot records with Power Fx
Add the Metadata Snapshots Dataverse table as a data source.
For a gallery showing recent inspections, use:
SortByColumns( 'Metadata Snapshots', "pea_inspectiondate", SortOrder.Descending)
The gallery can display the table logical name, attribute count, and inspection date.
9.3 Counting snapshots
For the initial small dataset:
CountRows('Metadata Snapshots')
Dataverse aggregation and delegation behavior must be considered when interpreting this result.
In particular, a count returned through Power Fx should not automatically be treated as a transactionally exact count for arbitrary dataset sizes.
9.4 Finding the latest inspection
First( SortByColumns( 'Metadata Snapshots', "pea_inspectiondate", SortOrder.Descending )).'Inspection Date'
The dashboard should also handle the case where no snapshots exist.
9.5 Counting distinct tables
For a small experimental dataset:
CountRows( Distinct( 'Metadata Snapshots', 'Table Logical Name' ))
This formula is useful for demonstrating the concept, but Distinct has delegation implications.
For larger datasets, the application should use a server-side aggregation or another design that guarantees the required accuracy.
9.6 Navigation and publishing
Add the Custom page to the Model-Driven App navigation.
Position it as the intended landing experience.
Publish the Custom page, then publish the Model-Driven App.
The application must be opened and tested to confirm the actual startup behavior.
Microsoft explicitly documents that Model-Driven Apps referencing a published Custom page must also be republished for the updated page to be used.
10. Executing and Validating the Application
Once the Dataverse table, C# project, Model-Driven App, and dashboard are ready, we can execute the complete experiment.
The intended execution sequence is:
Start Console App | vAuthenticate with Microsoft Entra ID | vConnect to Dataverse | vRetrieve Table Metadata | vRetrieve Account Attributes | vTransform Metadata into JSON | vCreate Metadata Snapshot | vRetrieve Persisted Record | vOpen Model-Driven App | vInspect Dashboard, View, and Form
Successful execution should produce a console trace indicating the connection, metadata retrieval, snapshot creation, and record verification.
The actual table count, attribute count, and snapshot identifier will depend on the environment.
We should record these values during implementation rather than invent them for the article.
10.1 Validation criteria
The experiment will be considered successful only when the following conditions have been verified:
| Component | Expected evidence |
|---|---|
| C# application | Successful build and execution |
| Authentication | Valid authenticated Dataverse connection |
| Metadata retrieval | Table and attribute definitions returned |
| Persistence | Snapshot record created |
| SDK verification | Persisted record retrieved |
| Model-Driven App | Application opens and displays data |
| Dashboard | Metrics and recent snapshots are visible |
| Security | Access behaves according to assigned privileges |
These checks ensure that the experiment validates the entire architecture rather than merely demonstrating an isolated SDK call.
11. Failure Injection and Diagnostic Analysis
Understanding failure behavior is essential to mastering an enterprise platform.
For APP-001, we will deliberately introduce several controlled failures.
11.1 Invalid table logical name
Change the target table to a nonexistent logical name.
const string targetTable = "pea_nonexistenttable";
Execute the metadata request and inspect the exception.
The objective is to understand how Dataverse reports an invalid metadata reference through the SDK.
11.2 Authentication failure
Configure an invalid application ID or redirect URI.
Observe the difference between configuration validation, authentication failure, and connection readiness.
Do not assume that every authentication failure produces the same exception.
11.3 Authorization failure
Execute the application using a test identity without the necessary permissions to create Metadata Snapshot records.
Metadata retrieval and record creation may have different authorization outcomes.
This experiment helps establish the distinction between authentication and authorization.
11.4 Oversized JSON payload
Increase the amount of serialized metadata until the payload exceeds the configured column capacity.
Observe the validation failure and determine whether the correct solution is to increase the supported column capacity, reduce the payload, normalize the data, or move large diagnostic content to another storage mechanism.
11.5 Repeated execution
Execute the program multiple times.
The current implementation creates a new record on every execution.
This behavior is useful for preserving inspection history, but it also raises questions about idempotency, duplicate records, and retention.
A future implementation could use an explicit snapshot version or a stable synchronization key.
12. Architectural Considerations for Production Systems
The initial Metadata Explorer is intentionally small, but the experiment introduces several issues that become important in enterprise implementations.
12.1 Metadata synchronization versus metadata discovery
Discovery retrieves definitions to understand the current environment.
Synchronization maintains a local representation of those definitions over time.
These are different problems.
A discovery tool may retrieve all tables during an occasional inspection.
A synchronization engine should consider incremental metadata retrieval and change detection.
Dataverse supports RetrieveMetadataChangesRequest for querying metadata changes.
12.2 Consistency and concurrent customization
Our program executes separate metadata requests.
If a customization is published between those requests, the results may not represent one atomic schema state.
This is a consistency consideration rather than necessarily a defect in the SDK.
A production metadata comparison tool should define what consistency guarantees it requires and how it handles changes occurring during inspection.
12.3 Security and least privilege
The C# application should use an identity with only the privileges required for its operations.
The Model-Driven App should expose snapshot records according to an appropriate security model.
The choice of organization-owned records does not remove the need for Dataverse security roles.
12.4 JSON snapshots versus normalized metadata tables
Storing metadata as JSON is convenient for inspection and diagnostics.
However, it makes individual attributes harder to query through native Dataverse views and relationships.
A normalized model might introduce separate tables for:
- Table definitions
- Attribute definitions
- Relationships
- Inspection executions
- Metadata changes
Such a design would support more advanced queries and comparisons, but it would also increase the implementation scope.
For APP-001, a compact snapshot is sufficient to establish the core metadata retrieval and persistence behavior.
12.5 SDK versus Web API
The Dataverse SDK and Web API expose related platform capabilities through different programming models.
The SDK is convenient for C# applications because it provides typed request and response classes.
The Web API is useful for HTTP-based integrations and applications written in other languages.
Neither approach eliminates the need to understand logical names, metadata contracts, authorization, and platform constraints.
A future experiment can compare equivalent metadata requests through the SDK and Web API, including payloads, diagnostics, and performance.
13. Lessons from the Architecture
Several engineering principles emerge from this experiment.
First, Dataverse metadata should be treated as a formal platform contract rather than merely configuration displayed in the Power Apps designer.
Second, retrieving metadata and retrieving business records are fundamentally different operations.
Third, a useful engineering application should make its technical behavior observable. The Model-Driven App gives us a separate interface for examining the output of our C# experiment.
Fourth, successful authentication, successful SDK execution, and successful persistence are separate conditions that must be validated independently.
Finally, implementation simplicity and architectural depth are not mutually exclusive.
A small Console App and one Dataverse table are sufficient to explore meaningful concepts involving metadata, identity, authorization, serialization, persistence, consistency, and application design.
14. Technical Assessment
The academy uses three levels of assessment for every application.
Beginner — Fundamentals: Explain the difference between EntityMetadata and Entity, the purpose of logical names, the meaning of EntityFilters, and the role of ServiceClient.
Senior — Implementation: Diagnose metadata retrieval and persistence failures, explain delegation considerations, design idempotent snapshot creation, and evaluate metadata request efficiency.
Expert — Architecture: Design incremental metadata synchronization, reason about concurrent schema changes, compare metadata across environments, and establish an appropriate security and consistency model.
The answers should be reviewed against the actual implementation evidence rather than treated as purely theoretical questions.
15. Official Microsoft Documentation
The following Microsoft Learn resources provide the foundation for the architecture and implementation discussed in this article.
- Customize table definitions
https://learn.microsoft.com/en-us/power-apps/developer/data-platform/customize-entity-metadata - Retrieve, update, and delete table definitions
https://learn.microsoft.com/en-us/power-apps/developer/data-platform/org-service/metadata-retrieve-update-delete-entities - Use OAuth authentication with Microsoft Dataverse
https://learn.microsoft.com/en-us/power-apps/developer/data-platform/authenticate-oauth - Dataverse SDK for .NET — Organization Service
https://learn.microsoft.com/en-us/power-apps/developer/data-platform/org-service/overview - Create a Model-Driven App
https://learn.microsoft.com/en-us/power-apps/maker/model-driven-apps/create-model-driven-app - Add a Custom page to a Model-Driven App
https://learn.microsoft.com/en-us/power-apps/maker/model-driven-apps/add-page-to-model-app - Use Power Fx in a Custom page
https://learn.microsoft.com/en-us/power-apps/maker/model-driven-apps/page-powerfx-in-model-app - Design a Custom page
https://learn.microsoft.com/en-us/power-apps/maker/model-driven-apps/design-page-for-model-app
Conclusion
Building a Dataverse Metadata Explorer is a practical way to understand the platform beyond its graphical development tools.
By retrieving metadata through the Dataverse SDK, transforming selected definitions into structured snapshots, persisting those snapshots, and presenting them through a Model-Driven App, we connect several layers of the Power Platform architecture.
The experiment demonstrates the distinction between metadata and business data, the importance of API contracts, the role of identity and authorization, and the value of making technical behavior observable.
More importantly, it establishes a foundation for future engineering work involving schema comparison, metadata synchronization, custom APIs, plugins, integration services, and enterprise application architecture.
APP-001 is the starting point of a larger engineering laboratory: one focused experiment at a time, with deep technical analysis, reproducible execution, and evidence-based conclusions.