Skip to content

Dataverse sample code modernization#767

Draft
phecke wants to merge 10 commits intomasterfrom
sample-migration-infrastructure
Draft

Dataverse sample code modernization#767
phecke wants to merge 10 commits intomasterfrom
sample-migration-infrastructure

Conversation

@phecke
Copy link
Member

@phecke phecke commented Feb 12, 2026

Upgrade the existing 128 Dataverse code samples that use deprecated APIs and an outdated code design. The end result is an improved customer learning experience and adherence to modern coding standards.

phecke and others added 10 commits February 6, 2026 11:48
Phase 1: Infrastructure Setup

Created automation tools and templates to support systematic migration
of 120+ legacy .NET Framework 4.7.2 samples to modern .NET 6+ architecture.

PowerShell Scripts:
- New-CategoryFolder.ps1: Creates category folder structure with standard files
- New-ModernSample.ps1: Scaffolds new sample projects within categories
- Convert-LegacySample.ps1: Assists with code transformation and modernization
- Test-MigratedSamples.ps1: Validates samples build successfully

Templates:
- Program.cs.template: Standard modern sample structure with Setup/Run/Cleanup
- Sample.csproj.template: SDK-style project with modern packages
- CategoryREADME.md.template: Category-level documentation template
- SampleREADME.md.template: Sample-level documentation template

These tools follow the established pattern documented in README-code-design.md
and will accelerate migration while ensuring consistency across all samples.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Created Audit category structure and migrated first sample:

New Category:
- Audit/ folder with standard structure
- Category-level appsettings.json and README.md
- Audit.slnx solution file

Completed Sample - AuditEntityData:
- Fully migrated from legacy .NET Framework to modern .NET 6+
- Converted from early-bound to late-bound entities
- Modernized to Setup/Run/Cleanup pattern
- Demonstrates:
  * Enabling organization and entity-level auditing
  * Retrieving record change history
  * Retrieving attribute change history
  * Displaying audit details with old/new values
- Complete README documentation
- Builds successfully (verified with dotnet build)

In Progress - AuditUserAccess:
- Project scaffolding created
- Implementation pending

This validates the automation scripts work correctly and establishes
the migration pattern for remaining 118+ samples.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Completed pilot category with both samples fully migrated and tested:

AuditUserAccess Sample (NEW - COMPLETE):
- Fully migrated from legacy .NET Framework to modern .NET 6+
- Converted from early-bound to late-bound entities
- Modernized to Setup/Run/Cleanup pattern
- Demonstrates:
  * Enabling organization and user access auditing
  * Retrieving user access audit records
  * Filtering audit records by action type and time
  * Displaying audit information with friendly names
- Complete README documentation with sample output
- Builds successfully (verified with dotnet build)

Category Updates:
- Updated Audit/README.md with complete samples table
- Both samples link to Microsoft Learn documentation
- Solution builds successfully with both projects

Testing Results:
- ✅ AuditEntityData builds successfully
- ✅ AuditUserAccess builds successfully
- ✅ Solution (Audit.slnx) builds successfully
- ✅ Zero build errors

Migration Progress: 2 of 120+ samples complete (Pilot Phase 2)

Next: Continue pilot with remaining categories (DuplicateDetection, Queues)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Migrated 3 duplicate detection samples from legacy .NET Framework to modern .NET 6:

1. EnableDuplicateDetection - Enables duplicate detection at org and entity levels,
   publishes rules with async tracking, and retrieves duplicate records

2. DetectMultipleDuplicateRecords - Creates duplicate detection rule programmatically,
   uses BulkDetectDuplicatesRequest for async bulk detection, queries duplicaterecord table

3. UseDuplicatedetectionforCRUD - Demonstrates SuppressDuplicateDetection parameter
   with CreateRequest and UpdateRequest to control duplicate detection behavior

All samples:
- Follow modern pattern with Setup/Run/Cleanup structure
- Use late-bound Entity syntax (no early-bound types)
- Include comprehensive README documentation
- Build successfully with zero errors
- Share appsettings.json configuration

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ogress)

Created Queues category infrastructure with 8 queue management samples.

Fully implemented:
1. CreateQueue - Creates queue with configuration options
2. DeleteQueue - Creates and deletes queue
3. AddRecordToQueue - Adds records to queues and moves between queues

Scaffolded (build successfully, implementation in progress):
4. ReleaseQueueItems - Release queue items from workers
5. SpecifyQueueItem - Assign queue items to workers
6. CleanHistoryQueue - Remove completed items from queues
7. ShareQueue - Share queue access with teams
8. AddSecurityPrincipalToQueue - Add security principals to queues

All samples:
- Build successfully with zero errors
- Follow modern pattern structure
- Share appsettings.json configuration
- Part of Phase 2 pilot migration

Phase 2 pilot progress: 5 of 12 samples fully complete
(Audit: 2, DuplicateDetection: 3, Queues: 3/8 implemented)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented remaining 5 Queue samples to complete the category:

1. ReleaseQueueItems - Release queue items from workers using ReleaseToQueueRequest
2. SpecifyQueueItem - Assign queue items to workers using PickFromQueueRequest
3. CleanHistoryQueue - Remove completed items from queues using RemoveFromQueueRequest
4. ShareQueue - Share queue access with teams using GrantAccessRequest
5. AddSecurityPrincipalToQueue - Add teams to queues using AddPrincipalToQueueRequest

All Queues samples (8/8):
- Build successfully (0 errors)
- Follow modern pattern (Setup/Run/Cleanup)
- Use late-bound Entity syntax
- Share appsettings.json configuration
- Demonstrate queue management SDK operations

Phase 2 Pilot COMPLETE:
- Audit: 2 samples ✓
- DuplicateDetection: 3 samples ✓
- Queues: 8 samples ✓
Total: 13 samples, 100% build success rate

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replaced template content with detailed documentation for each Queue sample:

- CreateQueue: Queue creation with configuration options
- DeleteQueue: Queue lifecycle management (create/delete)
- AddRecordToQueue: Adding and routing records between queues
- ReleaseQueueItems: Releasing queue items from worker assignments
- SpecifyQueueItem: Assigning queue items to specific workers
- CleanHistoryQueue: Removing completed items from queues
- ShareQueue: Sharing queue access with teams using GrantAccessRequest
- AddSecurityPrincipalToQueue: Adding teams as queue members

Each README includes:
- Comprehensive description of functionality
- Detailed Setup/Run/Cleanup workflow
- Key SDK concepts demonstrated
- Sample console output
- Links to Microsoft Learn documentation

Phase 2 Pilot (Queues category) documentation now complete.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Created Query category structure with:
- Category README and appsettings.json

Migrated 3 samples from legacy C# to modern .NET 6+:

1. RetrieveMultipleQueryByAttribute - Query records using QueryByAttribute
2. RetrieveMultipleByQueryExpression - Query with linked entities and aliases
3. UseQueryExpressionwithPaging - Demonstrate paging with PagingInfo and paging cookies

All samples follow modern pattern:
- ServiceClient (not CrmServiceClient)
- appsettings.json configuration
- Setup/Run/Cleanup structure
- Late-bound Entity syntax
- EntityStore tracking
- Comprehensive README documentation

All samples build successfully (0 errors).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Migrated 4 samples demonstrating FetchXML capabilities:

1. UseFetchXMLWithPaging - Paging with FetchXML using page and paging-cookie attributes
2. UseAggregationInFetchXML - 17 aggregate query patterns (AVG, COUNT, SUM, MIN, MAX, GROUP BY, date grouping)
3. Convertqueriesfetchqueryexpressions - Converting between FetchXML and QueryExpression
4. ValidateandExecuteSavedQuery - Creating, validating, and executing saved queries (views)

All samples:
- Use modern ServiceClient with appsettings.json
- Follow Setup/Run/Cleanup pattern
- Use late-bound Entity syntax
- Track entities in entityStore
- Build successfully (0 errors)
- Include comprehensive README documentation

Query category progress: 7 of 14 samples completed.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Migrated remaining 7 Query samples to complete the category:

1. RetrieveRecordsFromIntersectTable - Query many-to-many intersect tables (3 approaches)
2. QueryByReciprocalRole - Query connection roles by reciprocal role
3. QueryByRecord - Query connections for specific records
4. QueryWorkingHours - Query working hours schedules using QueryScheduleRequest
5. QueryHoursMultipleUsers - Query working hours for multiple users
6. QueriesUsingLINQ - 23 LINQ query patterns with OrganizationServiceContext
7. ExportDataUsingFetchXmlToAnnotation - Export query results to CSV annotations

All samples:
- Use modern ServiceClient with appsettings.json
- Follow Setup/Run/Cleanup pattern
- Use late-bound Entity syntax (including LINQ sample)
- Track entities in entityStore
- Build successfully (0 errors)
- Include comprehensive README documentation

Query category complete: 14 of 14 samples migrated.

Phase 3 Batch 1 (Core Operations) progress:
- Query: 14/14 samples ✓ COMPLETE
- CRUD: 0/7 samples (next)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@phecke phecke self-assigned this Feb 12, 2026
@phecke phecke marked this pull request as draft February 12, 2026 22:19
@JimDaly
Copy link
Member

JimDaly commented Feb 14, 2026

Rather than migrate old samples from the CSharp folder in to the CSharp-NETCore folder, I think all our modern .NET C# samples should be in the CSharp folder. We don't need a CSharp-NETCore folder.

We need a CSharp-NETFramework folder for all the old samples.

The center of gravity shifted already. .NET Framework is the exception to the rule.

Copy link
Member

@JimDaly JimDaly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't get to review everything. I got as far as Query, but I think there are some things to discuss.

/// <param name="entityLogicalName">The logical name of the entity</param>
/// <param name="flag">True to enable auditing, false to disable</param>
/// <returns>The previous value of the IsAuditEnabled attribute</returns>
private static bool EnableEntityAuditing(ServiceClient service, string entityLogicalName, bool flag)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private static bool EnableEntityAuditing(ServiceClient service, string entityLogicalName, bool flag)

Unless the method is using one of the methods added by ServiceClient, this parameter should be an IOrganizationService interface.

/// <param name="entityLogicalName">The logical name of the entity</param>
/// <param name="flag">True to enable auditing, false to disable</param>
/// <returns>The previous value of the IsAuditEnabled attribute</returns>
private static bool EnableEntityAuditing(ServiceClient service, string entityLogicalName, bool flag)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this function is confusing. EnableEntityAuditing should do one thing: Enable entity auditing. There should not be any flag value.

The function should check if auditing is enabled and enable it if it isn't enabled.
If it is already enabled, do nothing.

Otherwise, change the name of the function to EnableOrDisableEntityAuditing - but recommend just changing the signature and behavior

This same function is defined in the next AuditUserAccess sample.
Can't we define it in a single place within this Audit folder and reuse it?

/// </summary>
/// <param name="service">The service client</param>
/// <param name="detail">The audit detail to display</param>
private static void DisplayAuditDetails(ServiceClient service, AuditDetail detail)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private static void DisplayAuditDetails(ServiceClient service, AuditDetail detail)

I don't see that the service parameter is used anywhere in this function. Remove it?

ServiceClient serviceClient =
new(app.Configuration.GetConnectionString("default"));

if (!serviceClient.IsReady)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This IsReady check isn't necessary. See my email. This is cargo-cult boilerplate

/// <summary>
/// Sets up sample data by enabling auditing and creating an account
/// </summary>
private static void Setup(ServiceClient service)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a global comment.

Unless a method uses some ServiceClient.method, the parameter should be IOrganizationService, which ServiceClient implements.

/// <summary>
/// Enables duplicate detection for a specific entity
/// </summary>
private static void EnableDuplicateDetectionForEntity(ServiceClient service, string entityName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some doubts about whether it is necessary to publish the changes to the EntityMetadata. Retrieving metadata using RetrieveAsIfPublished is a documented anti-pattern.

What is more common is using Strong Consistency when retrieving changes. With SDK this is by setting the ServiceClient.ForceServerMetadataCacheConsistency Property

/// <summary>
/// Retrieves the organization ID
/// </summary>
private static Guid? RetrieveOrganizationId(ServiceClient service)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use WhoAmI to get WhoAmIResponse.OrganizationId at the start and cache it?

Or ServiceClient.ConnectedOrgId Property

/// <summary>
/// Waits for async jobs to complete
/// </summary>
private static void WaitForAsyncJobCompletion(ServiceClient service, IEnumerable<Guid> asyncJobIds)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having this reusable function defined at a higher level would demonstrate DRY coding.

}

attempts++;
Thread.Sleep(1000);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See earlier comment about code smell using Thread.Sleep.

- Bypass duplicate detection when needed
- Allow duplicate detection to run when needed

The SuppressDuplicateDetection parameter provides fine-grained control over when duplicate detection fires during CRUD operations, allowing developers to choose when to enforce or bypass duplicate detection rules.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one of several 'optional parameters'. We should use this term.
We should have a link to this section Suppress duplicate detection

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants