Dataverse VirtualEntity provider for Azure Storage Tables! No more logs in SQL - store everything for free on Storage :) just kidding about that "free" part, but try it out anyway :)
Download the latest managed solution zip from the GitHub Releases page and import it into your Dataverse environment. After the import completes, open the TableBridge Config model-driven app to configure connection records and field mappings.
The connection string for Azure Storage is stored in a custom Dataverse entity named new_azuretableconnection. Multiple records can be created, one for each virtual entity. The entity includes:
new_connectionstring– the Azure Storage connection string.new_idlocation– where the Dataverse ID is stored:partition,row, orcolumn.new_idcolumn– whennew_idlocationiscolumn, the name of the Azure Table column that holds the ID.new_entityname– logical name of the virtual entity this configuration applies to.new_tablename– name of the Azure Table that stores rows for this entity.
If new_tablename is not provided, the plugin defaults to using the logical entity name as the table name.
The plugins locate the appropriate record based on the entity logical name when they execute.
Each Dataverse message is implemented as its own plugin class. These plugins read the connection information from the custom entity and then use the Azure.Data.Tables SDK to operate on rows in the corresponding Azure Table. The classes are:
-
CreatePlugin -
RetrievePlugin -
RetrieveMultiplePlugin -
UpdatePlugin -
GetTableFieldsPlugin -
CreateMissingAttributesPlugin -
DeletePlugin -
Create – Generates a GUID for the record when one isn't supplied, checks the table to ensure the ID isn't already used, then inserts the row and returns the ID.
-
Retrieve – Fetches a single row using the configured ID location and converts the table entity into a Dataverse
Entity. -
RetrieveMultiple – Queries the table using the
QueryExpressionpassed from Dataverse. The plugin builds the OData filter from the expression (including nestedAND/ORgroups) and only returns the attributes requested in theColumnSet. -
Update – Upserts the corresponding table row with the provided attributes.
-
Delete – Removes the row from the table.
-
GetTableFieldsPlugin – Returns the available Azure Table columns and their types.
-
CreateMissingAttributesPlugin – Creates Dataverse string attributes for the selected columns and sets ExternalName to the table column.
The ID for each Dataverse record is stored in the table according to new_idlocation. When set to column, the value is read from the column specified in new_idcolumn.
This project is intended as a starting point and is not ready for production use without further work. "Not for production" means the plugins only implement the bare minimum logic to demonstrate how a virtual entity provider could communicate with Azure Table Storage. There is almost no validation or error handling, no retry logic and no authentication/authorization story for the storage account. The code also assumes a very small set of data types and does not account for concurrency conflicts.
To evolve this into a production quality solution you should:
- Harden the error handling across all plugins and log failures.
- Secure the connection string and use managed identity or a key vault to access it.
- Add retry policies for transient storage errors and handle concurrency conflicts.
- Expand type support and validation so unexpected attribute values do not break the provider.
- Create automated unit and integration tests that run on every build.
The solution includes a console application under tests/PluginTests that uses
FakeXrmEasy to spin up a
local Dataverse-like environment. Each plugin has a corresponding test class
with a Run() method that can be executed to manually test the logic without
deploying anything to Dataverse.
Run all tests with:
dotnet run --project tests/PluginTests/PluginTests.csprojAutomated unit tests are located under tests/UnitTests and use xUnit together
with FakeXrmEasy. These mirror the manual scenarios and can be executed with:
dotnet test tests/UnitTests/UnitTests.csprojThe test project includes optional integration tests that connect to a real
Dataverse environment while still executing the plugins locally. Store the
connection string and target entity logical name in user secrets under the keys
ConnectionString and EntityName:
dotnet user-secrets set ConnectionString "<your dataverse connection string>"
dotnet user-secrets set EntityName "account"Running DataverseIntegrationTests.Run() will create a real service connection
using these details and then invoke the Retrieve and RetrieveMultiple
plugins through a local service provider. The results are printed to the
console so you can verify the plugin logic against live data.
The repository includes a Solution folder containing the unpacked Dataverse solution with the new_azuretableconnection entity definition. The script Solution/package-solution.ps1 builds the plugins, copies the merged assembly into the solution and then uses the Solution Packager via pac to create a managed zip:
# pack the solution into a managed zip
./Solution/package-solution.ps1The script outputs Solution/out/TableBridge_Managed.zip. Ensure the Power Platform CLI (pac) is installed and available on your PATH.
A GitHub Actions workflow (.github/workflows/build-release.yml) packages the managed solution whenever a tag starting with v is pushed or when triggered manually. The job builds the plugins, updates the unpacked solution, packs it with pac, and uploads the zip as a release asset. No secrets are required; the workflow uses the built-in GITHUB_TOKEN.
The Seed console project can populate an Azure Table with 10,000 sample
customer rows. Update the connection string and table name in
tools/Seed/Program.cs and run:
dotnet run --project tools/Seed/Seed.csprojThe repository includes a workspace setting that places Azurite's data files under .azurite_data. This keeps the emulator storage out of the source tree. When opening the project in VS Code, the emulator will automatically use this location.
The solution includes several sample web resources under Solution/WebResources to help set up the provider inside Dataverse:
TableStorageConfigPage.html– simple page with a button that opens thenew_azuretableconnectiontable so you can create configuration records.DatasourceBanner.js– on-load script that notifies when the required Virtual Entity Data Source record is missing and provides a button to create it.ConfigEntityBanner.js– on-load script for the configuration form. It checks if the mapped Dataverse entity exists and, if not, offers a button to create it. The notification only appears after the Data Source has been created.FieldMappingSummary.js– on-load script that calls a plugin to read the Azure Table schema and shows which fields are already mapped. When creating missing columns it prompts for a logical name (default 'lsx_' prefix) and display name. The plugin stores the table column in the ExternalName property.
These resources are intended as lightweight helpers when experimenting with the virtual entity provider.
