Following on from Dynamics CRM Paging Cookies – Some gotchas!, here is a helper class we use for all our paging needs.

An example of its usage:

const string fetchXml = @"
    <fetch mapping='logical' count='5000' version='1.0' page='{0}' {1}>
	    <entity name='account'>
		    <attribute name='name' />
	    </entity>
    </fetch>";

var accounts = new PagedRetriever<Tuple<Guid, string>>(
                    entity => Tuple.Create( entity.Id, 
                                            (string)entity.Attributes["name"]), 
                    (page, cookie) => String.Format(fetchXml, page, cookie))
                .GetData(service);

The utility class has a couple of nice features:

    Laziness – Extra pages are only retrieved as they are enumerated
    Memory efficient – Entities are converted on the fly, saving a huge amount of memory if a large enumeration is converted to a list

Here is the complete code for the Paged Retriever class:

public class PagedRetriever<T>
{
    private readonly Func<Entity, T> converter;
    private readonly Func<int, string, string> pagedFetchXml;
    private readonly bool usePagingCookie;

    /// <summary>
    /// Utility class to retrieve paged results from CRM
    /// </summary>
    /// <param name="converter">Converts the returned entity to a wrapper type</param>
    /// <param name="pagedFetchXml">pagedFetchXml takes the pagenumber and the pagingCookie and returns the fetchXml</param>
    /// <param name="usePagingCookie">Whether the paging cookie should be used. Set to yes for performance if the primary entity id is unique in the resultset.</param>
    public PagedRetriever(Func<Entity, T> converter, Func<int, string, string> pagedFetchXml, bool usePagingCookie = true)
    {
        this.converter = converter;
        this.pagedFetchXml = pagedFetchXml;
        this.usePagingCookie = usePagingCookie;
    }


    /// <summary>
    /// Get all entities using the standard RetrieveMultiple call
    /// </summary>
    /// <param name="service"></param>
    /// <returns></returns>
    public IEnumerable<T> GetData(IOrganizationService service)
    {
        return GetData(query => service.RetrieveMultiple(query));
    }

    /// <summary>
    /// Gets all entities using a custom collection producer (eg. a RetrieveByResourcesServiceRequest)
    /// </summary>
    /// <param name="retriever"></param>
    /// <returns></returns>
    public IEnumerable<T> GetData(Func<QueryBase, EntityCollection> retriever)
    {
        int page = 1;
        string pagingCookie = String.Empty;
        while (true)
        {
            var pagedXml = pagedFetchXml(page, pagingCookie);

            var entityCollection = retriever(new FetchExpression(pagedXml));
            if (entityCollection == null)
            {
                break;
            }

            foreach (var convertedEntity in entityCollection.Entities.Select(converter))
            {
                yield return convertedEntity;
            }

            if (!entityCollection.MoreRecords)
            {
                yield break;
            }

            if (usePagingCookie && !String.IsNullOrEmpty(entityCollection.PagingCookie))
            {
                pagingCookie = "paging-cookie='" + System.Web.HttpUtility.HtmlEncode(entityCollection.PagingCookie) + "'";
            }

            page++;
        }
    }
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s