Skip to content

lsuhadolnik/TableBridge

Repository files navigation

TableBridge

TableBridge Logo

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 :)

How to install this?

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.

Interface in Dynamics CRM / Dataverse

Connection String Storage

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, or column.
  • new_idcolumn – when new_idlocation is column, 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.

Plugins

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 QueryExpression passed from Dataverse. The plugin builds the OData filter from the expression (including nested AND/OR groups) and only returns the attributes requested in the ColumnSet.

  • 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:

  1. Harden the error handling across all plugins and log failures.
  2. Secure the connection string and use managed identity or a key vault to access it.
  3. Add retry policies for transient storage errors and handle concurrency conflicts.
  4. Expand type support and validation so unexpected attribute values do not break the provider.
  5. Create automated unit and integration tests that run on every build.

Local Plugin Tests

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.csproj

Automated 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.csproj

Integration Tests

The 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.

Packaging the Solution

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.ps1

The script outputs Solution/out/TableBridge_Managed.zip. Ensure the Power Platform CLI (pac) is installed and available on your PATH.

Automated Build & Release

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.

Seeding Test Data

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.csproj

VS Code Azurite Location

The 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.

Configuration Web Resources

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 the new_azuretableconnection table 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.

About

Now you can connect Azure Storage Tables to Dynamics/PowerApps as Virtual entities!

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors