Server-Side FetchXML Paging Using a Dynamics 365 Plugin
If you're working server-side inside a Plugin or Custom Workflow Activity, you can bypass client-side paging limits and fully control how many records to pull using FetchXML and paging cookies.
When to Use a Plugin for This
- Fetching large datasets for processing on the server
- Validating or aggregating records during business logic
- Keeping logic secure and non-reversible from the client
- Integrating real-time, record-heavy automations
Step 1: Base FetchXML Structure
Start with this structure:
<fetch mapping='logical' count='5000'>
<entity name='account'>
<attribute name='accountid' />
<attribute name='name' />
<filter>
<condition attribute='statecode' operator='eq' value='0' />
</filter>
</entity>
</fetch>
💡 Tip: Keep your base FetchXML free of paging logic—this will be dynamically injected.
Step 2: Plugin Implementation
This plugin fetches all active account records, 5000 at a time, using the PagingCookie and MoreRecords flags.
public class FetchAllAccountsPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = factory.CreateOrganizationService(context.UserId);
string pagingCookie = null;
int pageNumber = 1;
int fetchCount = 5000;
bool moreRecords = true;
List<Entity> allAccounts = new List<Entity>();
while (moreRecords)
{
string fetchXml = CreateFetchXml(pagingCookie, pageNumber, fetchCount);
EntityCollection results = service.RetrieveMultiple(new FetchExpression(fetchXml));
allAccounts.AddRange(results.Entities);
tracer.Trace($"Fetched page {pageNumber}: {results.Entities.Count} records");
moreRecords = results.MoreRecords;
if (moreRecords)
{
pagingCookie = results.PagingCookie;
pageNumber++;
}
}
tracer.Trace($"✅ Total records fetched: {allAccounts.Count}");
}
private string CreateFetchXml(string pagingCookie, int pageNumber, int fetchCount)
{
return $@"
<fetch mapping='logical' page='{pageNumber}' count='{fetchCount}'{(pagingCookie != null ? $" paging-cookie='{{System.Security.SecurityElement.Escape(pagingCookie)}}'" : "")}>
<entity name='account'>
<attribute name='accountid' />
<attribute name='name' />
<filter>
<condition attribute='statecode' operator='eq' value='0' />
</filter>
</entity>
</fetch>";
}
}
⚠️ Watch out: Don’t forget to escape the pagingCookie! Use
System.Security.SecurityElement.Escape() to avoid breaking XML syntax.
Bonus Tips
- Use an async plugin or Custom API for long-running tasks
- Consider streaming results or batching when working with >50,000 records
- For external systems, push the results into Azure or a Data Export Service
Registration Ideas
This plugin can be triggered via:
- A
Custom API(for on-demand calls) - A
WorkfloworPower Automate Flow - Or bound to Entity Events like Create/Update (with filters)
No comments:
Post a Comment