Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions .github/workflows/publish-nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,54 @@ jobs:
runs-on: windows-latest

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v5
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
8.0.x
10.0.x

- name: Get version from tag
id: version
shell: bash
run: |
# Extract version from tag (v1.0.0-alpha.1 -> 1.0.0-alpha.1)
VERSION=${GITHUB_REF#refs/tags/v}
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "Publishing version: $VERSION"

- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --configuration Release --no-restore
run: dotnet build --configuration Release --no-restore -p:Version=${{ steps.version.outputs.VERSION }}

- name: Pack
run: dotnet pack --configuration Release --no-build --output ./nupkgs
run: dotnet pack --configuration Release --no-build --output ./nupkgs -p:Version=${{ steps.version.outputs.VERSION }}

- name: List packages
shell: bash
run: ls -la ./nupkgs/

- name: Push to NuGet
- name: Push packages to NuGet
shell: bash
run: dotnet nuget push ./nupkgs/PPDS.Plugins.*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
run: |
for package in ./nupkgs/*.nupkg; do
echo "Pushing $package"
dotnet nuget push "$package" --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json --skip-duplicate
done

- name: Push symbols to NuGet
shell: bash
run: dotnet nuget push ./nupkgs/PPDS.Plugins.*.snupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
run: |
for package in ./nupkgs/*.snupkg; do
echo "Pushing $package"
dotnet nuget push "$package" --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json --skip-duplicate
done
continue-on-error: true
26 changes: 23 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
# Changelog

All notable changes to PPDS.Plugins will be documented in this file.
All notable changes to the PPDS SDK packages will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- **PPDS.Migration.Cli** - New CLI tool for high-performance Dataverse data migration
- Commands: `export`, `import`, `analyze`, `migrate`
- JSON progress output for tool integration (`--json` flag)
- Support for multiple Application Users and bypass options
- Packaged as .NET global tool (`ppds-migrate`)
- Comprehensive unit test suite (98 tests)
- Targets: `net8.0`, `net10.0`

- **PPDS.Dataverse** - New package for high-performance Dataverse connectivity
- Multi-connection pool supporting multiple Application Users for load distribution
- Connection selection strategies: RoundRobin, LeastConnections, ThrottleAware
- Throttle tracking with automatic routing away from throttled connections
- Bulk operation wrappers: CreateMultiple, UpdateMultiple, UpsertMultiple, DeleteMultiple
- DI integration via `AddDataverseConnectionPool()` extension method
- Affinity cookie disabled by default for improved throughput
- Targets: `net8.0`, `net10.0`

### Changed

- Updated target frameworks: dropped `net6.0` (out of support), added `net10.0` (current LTS)
- Now targets: `net462`, `net8.0`, `net10.0`
- Updated publish workflow to support multiple packages and extract version from git tag
- Updated target frameworks for PPDS.Plugins: dropped `net6.0` (out of support), added `net10.0` (current LTS)
- PPDS.Plugins now targets: `net462`, `net8.0`, `net10.0`

## [1.1.0] - 2025-12-16

Expand Down
60 changes: 60 additions & 0 deletions PPDS.Sdk.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PPDS.Plugins.Tests", "tests\PPDS.Plugins.Tests\PPDS.Plugins.Tests.csproj", "{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PPDS.Dataverse", "src\PPDS.Dataverse\PPDS.Dataverse.csproj", "{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PPDS.Dataverse.Tests", "tests\PPDS.Dataverse.Tests\PPDS.Dataverse.Tests.csproj", "{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PPDS.Migration.Cli", "src\PPDS.Migration.Cli\PPDS.Migration.Cli.csproj", "{10DA306C-4AB2-464D-B090-3DA7B18B1C08}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PPDS.Migration.Cli.Tests", "tests\PPDS.Migration.Cli.Tests\PPDS.Migration.Cli.Tests.csproj", "{45DB0E17-0355-4342-8218-2FD8FA545157}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -45,12 +53,64 @@ Global
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Release|x64.Build.0 = Release|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Release|x86.ActiveCfg = Release|Any CPU
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0}.Release|x86.Build.0 = Release|Any CPU
{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0}.Debug|x64.ActiveCfg = Debug|Any CPU
{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0}.Debug|x64.Build.0 = Debug|Any CPU
{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0}.Debug|x86.ActiveCfg = Debug|Any CPU
{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0}.Debug|x86.Build.0 = Debug|Any CPU
{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0}.Release|Any CPU.Build.0 = Release|Any CPU
{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0}.Release|x64.ActiveCfg = Release|Any CPU
{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0}.Release|x64.Build.0 = Release|Any CPU
{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0}.Release|x86.ActiveCfg = Release|Any CPU
{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0}.Release|x86.Build.0 = Release|Any CPU
{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1}.Debug|x64.ActiveCfg = Debug|Any CPU
{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1}.Debug|x64.Build.0 = Debug|Any CPU
{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1}.Debug|x86.ActiveCfg = Debug|Any CPU
{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1}.Debug|x86.Build.0 = Debug|Any CPU
{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1}.Release|Any CPU.Build.0 = Release|Any CPU
{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1}.Release|x64.ActiveCfg = Release|Any CPU
{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1}.Release|x64.Build.0 = Release|Any CPU
{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1}.Release|x86.ActiveCfg = Release|Any CPU
{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1}.Release|x86.Build.0 = Release|Any CPU
{10DA306C-4AB2-464D-B090-3DA7B18B1C08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{10DA306C-4AB2-464D-B090-3DA7B18B1C08}.Debug|Any CPU.Build.0 = Debug|Any CPU
{10DA306C-4AB2-464D-B090-3DA7B18B1C08}.Debug|x64.ActiveCfg = Debug|Any CPU
{10DA306C-4AB2-464D-B090-3DA7B18B1C08}.Debug|x64.Build.0 = Debug|Any CPU
{10DA306C-4AB2-464D-B090-3DA7B18B1C08}.Debug|x86.ActiveCfg = Debug|Any CPU
{10DA306C-4AB2-464D-B090-3DA7B18B1C08}.Debug|x86.Build.0 = Debug|Any CPU
{10DA306C-4AB2-464D-B090-3DA7B18B1C08}.Release|Any CPU.ActiveCfg = Release|Any CPU
{10DA306C-4AB2-464D-B090-3DA7B18B1C08}.Release|Any CPU.Build.0 = Release|Any CPU
{10DA306C-4AB2-464D-B090-3DA7B18B1C08}.Release|x64.ActiveCfg = Release|Any CPU
{10DA306C-4AB2-464D-B090-3DA7B18B1C08}.Release|x64.Build.0 = Release|Any CPU
{10DA306C-4AB2-464D-B090-3DA7B18B1C08}.Release|x86.ActiveCfg = Release|Any CPU
{10DA306C-4AB2-464D-B090-3DA7B18B1C08}.Release|x86.Build.0 = Release|Any CPU
{45DB0E17-0355-4342-8218-2FD8FA545157}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{45DB0E17-0355-4342-8218-2FD8FA545157}.Debug|Any CPU.Build.0 = Debug|Any CPU
{45DB0E17-0355-4342-8218-2FD8FA545157}.Debug|x64.ActiveCfg = Debug|Any CPU
{45DB0E17-0355-4342-8218-2FD8FA545157}.Debug|x64.Build.0 = Debug|Any CPU
{45DB0E17-0355-4342-8218-2FD8FA545157}.Debug|x86.ActiveCfg = Debug|Any CPU
{45DB0E17-0355-4342-8218-2FD8FA545157}.Debug|x86.Build.0 = Debug|Any CPU
{45DB0E17-0355-4342-8218-2FD8FA545157}.Release|Any CPU.ActiveCfg = Release|Any CPU
{45DB0E17-0355-4342-8218-2FD8FA545157}.Release|Any CPU.Build.0 = Release|Any CPU
{45DB0E17-0355-4342-8218-2FD8FA545157}.Release|x64.ActiveCfg = Release|Any CPU
{45DB0E17-0355-4342-8218-2FD8FA545157}.Release|x64.Build.0 = Release|Any CPU
{45DB0E17-0355-4342-8218-2FD8FA545157}.Release|x86.ActiveCfg = Release|Any CPU
{45DB0E17-0355-4342-8218-2FD8FA545157}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{1E79DC81-59E1-4E4F-8B73-7F05E99F03F4} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{C7CC0394-6DE6-44C6-A6F3-EC9F5376B0D0} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{B1B07978-1CCC-4DE3-A9AD-2E0B10DF6CB0} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{738F9CC6-9EAC-4EA0-9B8B-DD6A5157A1F1} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{10DA306C-4AB2-464D-B090-3DA7B18B1C08} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{45DB0E17-0355-4342-8218-2FD8FA545157} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
EndGlobalSection
EndGlobal
162 changes: 50 additions & 112 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,156 +1,94 @@
# PPDS.Plugins
# PPDS SDK

[![Build](https://github.com/joshsmithxrm/ppds-sdk/actions/workflows/build.yml/badge.svg)](https://github.com/joshsmithxrm/ppds-sdk/actions/workflows/build.yml)
[![NuGet](https://img.shields.io/nuget/v/PPDS.Plugins.svg)](https://www.nuget.org/packages/PPDS.Plugins/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Plugin development attributes for Microsoft Dataverse. Part of the [Power Platform Developer Suite](https://github.com/joshsmithxrm/power-platform-developer-suite) ecosystem.
NuGet packages for Microsoft Dataverse development. Part of the [Power Platform Developer Suite](https://github.com/joshsmithxrm/power-platform-developer-suite) ecosystem.

## Overview
## Packages

PPDS.Plugins provides declarative attributes for configuring Dataverse plugin registrations directly in your plugin code. These attributes are extracted by [PPDS.Tools](https://github.com/joshsmithxrm/ppds-tools) to generate registration files that can be deployed to any environment.
| Package | NuGet | Description |
|---------|-------|-------------|
| **PPDS.Plugins** | [![NuGet](https://img.shields.io/nuget/v/PPDS.Plugins.svg)](https://www.nuget.org/packages/PPDS.Plugins/) | Declarative plugin registration attributes |
| **PPDS.Dataverse** | [![NuGet](https://img.shields.io/nuget/v/PPDS.Dataverse.svg)](https://www.nuget.org/packages/PPDS.Dataverse/) | High-performance connection pooling and bulk operations |

## Installation
---

```bash
dotnet add package PPDS.Plugins
```
## PPDS.Plugins

Or via the NuGet Package Manager:
Declarative attributes for configuring Dataverse plugin registrations directly in code.

```powershell
Install-Package PPDS.Plugins
```bash
dotnet add package PPDS.Plugins
```

## Usage

### Basic Plugin Step

```csharp
using PPDS.Plugins;

[PluginStep(
Message = "Create",
EntityLogicalName = "account",
Stage = PluginStage.PostOperation)]
public class AccountCreatePlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Plugin implementation
}
}
```

### Plugin with Filtering Attributes

```csharp
[PluginStep(
Message = "Update",
EntityLogicalName = "contact",
Stage = PluginStage.PreOperation,
Mode = PluginMode.Synchronous,
FilteringAttributes = "firstname,lastname,emailaddress1")]
public class ContactUpdatePlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Only triggers when specified attributes change
}
}
```

### Plugin with Images

```csharp
[PluginStep(
Message = "Update",
EntityLogicalName = "account",
Stage = PluginStage.PostOperation)]
[PluginImage(
ImageType = PluginImageType.PreImage,
Name = "PreImage",
Attributes = "name,telephone1,revenue")]
public class AccountAuditPlugin : IPlugin
Attributes = "name,telephone1")]
public class AccountCreatePlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Access pre-image via context.PreEntityImages["PreImage"]
}
public void Execute(IServiceProvider serviceProvider) { }
}
```

### Asynchronous Plugin
See [PPDS.Plugins on NuGet](https://www.nuget.org/packages/PPDS.Plugins/) for details.

```csharp
[PluginStep(
Message = "Create",
EntityLogicalName = "email",
Stage = PluginStage.PostOperation,
Mode = PluginMode.Asynchronous)]
public class EmailNotificationPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
// Runs in background via async service
}
}
```
---

## Attributes
## PPDS.Dataverse

### PluginStepAttribute
High-performance Dataverse connectivity with connection pooling, throttle-aware routing, and bulk operations.

Defines how a plugin is registered in Dataverse.

| Property | Type | Description |
|----------|------|-------------|
| `Message` | string | SDK message name (Create, Update, Delete, etc.) |
| `EntityLogicalName` | string | Target entity logical name |
| `Stage` | PluginStage | Pipeline stage (PreValidation, PreOperation, PostOperation) |
| `Mode` | PluginMode | Execution mode (Synchronous, Asynchronous) |
| `FilteringAttributes` | string | Comma-separated attributes that trigger the plugin |
| `ExecutionOrder` | int | Order when multiple plugins registered for same event |
| `Name` | string | Display name for the step |
| `StepId` | string | Unique ID for associating images with specific steps |
```bash
dotnet add package PPDS.Dataverse
```

### PluginImageAttribute
```csharp
// Setup
services.AddDataverseConnectionPool(options =>
{
options.Connections.Add(new DataverseConnection("Primary", connectionString));
options.Pool.DisableAffinityCookie = true; // 10x+ throughput improvement
});

Defines pre/post images for a plugin step.
// Usage
await using var client = await pool.GetClientAsync();
var account = await client.RetrieveAsync("account", id, new ColumnSet(true));
```

| Property | Type | Description |
|----------|------|-------------|
| `ImageType` | PluginImageType | PreImage, PostImage, or Both |
| `Name` | string | Key to access image in plugin context |
| `Attributes` | string | Comma-separated attributes to include |
| `EntityAlias` | string | Entity alias (defaults to Name) |
| `StepId` | string | Associates image with specific step |
See [PPDS.Dataverse documentation](src/PPDS.Dataverse/README.md) for details.

## Enums
---

### PluginStage
## Architecture Decisions

- `PreValidation (10)` - Before main system validation
- `PreOperation (20)` - Before main operation, within transaction
- `PostOperation (40)` - After main operation
Key design decisions are documented as ADRs:

### PluginMode
- [ADR-0001: Disable Affinity Cookie by Default](docs/adr/0001_DISABLE_AFFINITY_COOKIE.md)
- [ADR-0002: Multi-Connection Pooling](docs/adr/0002_MULTI_CONNECTION_POOLING.md)
- [ADR-0003: Throttle-Aware Connection Selection](docs/adr/0003_THROTTLE_AWARE_SELECTION.md)

- `Synchronous (0)` - Immediate execution, blocks operation
- `Asynchronous (1)` - Background execution via async service
## Patterns

### PluginImageType
- [Connection Pooling](docs/architecture/CONNECTION_POOLING_PATTERNS.md) - When and how to use connection pooling
- [Bulk Operations](docs/architecture/BULK_OPERATIONS_PATTERNS.md) - High-throughput data operations

- `PreImage (0)` - Entity state before operation
- `PostImage (1)` - Entity state after operation
- `Both (2)` - Both pre and post images
---

## Related Projects

- [power-platform-developer-suite](https://github.com/joshsmithxrm/power-platform-developer-suite) - VS Code extension
- [ppds-tools](https://github.com/joshsmithxrm/ppds-tools) - PowerShell deployment module
- [ppds-alm](https://github.com/joshsmithxrm/ppds-alm) - CI/CD pipeline templates
- [ppds-demo](https://github.com/joshsmithxrm/ppds-demo) - Reference implementation
| Project | Description |
|---------|-------------|
| [power-platform-developer-suite](https://github.com/joshsmithxrm/power-platform-developer-suite) | VS Code extension |
| [ppds-tools](https://github.com/joshsmithxrm/ppds-tools) | PowerShell deployment module |
| [ppds-alm](https://github.com/joshsmithxrm/ppds-alm) | CI/CD pipeline templates |
| [ppds-demo](https://github.com/joshsmithxrm/ppds-demo) | Reference implementation |

## License

Expand Down
Loading
Loading