jeudi 17 mars 2016

Requêter plus de 5 000 enregistrements

Vous vous êtes sûrement déjà confronté à la limite des 5 000 enregistrements lors de l'exécution d'une requête.
Aujourd'hui nous allons voir comment dépasser cette limite.

Pour bien comprendre ce qui va suivre, il faut savoir que MS CRM limite les requêtes à 5 000 enregistrements retournés pour une raison de performance.
Afin d'en récupérer au delà de cette limite il faut récupérer par lot jusqu'à temps qu'il n'y ait plus d'autres enregistrements à récupérer (propriété MoreRecords à false). De plus, il faut que le système sache à partir de quel enregistrement il faut repartir à chaque lot (propriété PagingCookie).
Pour ma part, je vous recommanderait de limiter vos lots à 250 enregistrements car récupérer 5000 enregistrements peut prendre un certain temps !
L'utilisation du PagingCookie est très importante pour optimiser les performances car sinon le système exécutera sa requête complètement et ne renverra que la page souhaitée alors qu'en le précisant la requête sera exécutée uniquement sur les données concernées.
Un petit schéma pour expliciter mon propos :

QueryExpression 

// Requête pour récupérer l'intégralité des comptes
QueryExpression query = new QueryExpression
{
    EntityName = "account",
    ColumnSet = new ColumnSet(true),
    PageInfo = new PagingInfo // Initialisation des informations de pagination
    {
        PageNumber = 1,
        PagingCookie = null,
        Count = 250 // 250 enregistrements seront récupérés à chaque lot
    }
};

EntityCollection result = null;

// Récupère les enregistrements par lot de 250 jusqu'à ce qu'ils soient tous récupérés
do
{
    result = service.RetrieveMultiple(query); // service est une instance de l'interface IOrganizationService

    // Faire quelque chose avec result

    query.PageInfo.PagingCookie = result.PagingCookie;
    query.PageInfo.PageNumber++;
} while (result.MoreRecords);

FetchXML

En FetchXML c'est un peu plus compliqué car il est nécessaire de modifier le XML, pour ce faire nous allons créer des fonctions facilitant ces opérations :
public string ExtractNodeValue(XmlNode parentNode, string name)
{
    XmlNode childNode = parentNode.SelectSingleNode(name);

    if (null == childNode)
    {
        return null;
    }
    return childNode.InnerText;
}

public string ExtractAttribute(XmlDocument doc, string name)
{
    XmlAttributeCollection attrs = doc.DocumentElement.Attributes;
    XmlAttribute attr = (XmlAttribute)attrs.GetNamedItem(name);
    if (null == attr)
    {
        return null;
    }
    return attr.Value;
}

public string CreateFetchXml(string fetchXmlQuery, string cookie, int page, int count)
{
    StringReader stringReader = new StringReader(fetchXmlQuery);
    XmlTextReader reader = new XmlTextReader(stringReader);

    // Load document
    XmlDocument doc = new XmlDocument();
    doc.Load(reader);

    return CreateFetchXml(doc, cookie, page, count);
}

public string CreateFetchXml(XmlDocument doc, string cookie, int page, int count)
{
    XmlAttributeCollection attrs = doc.DocumentElement.Attributes;

    if (cookie != null)
    {
        XmlAttribute pagingAttr = doc.CreateAttribute("paging-cookie");
        pagingAttr.Value = cookie;
        attrs.Append(pagingAttr);
    }

    XmlAttribute pageAttr = doc.CreateAttribute("page");
    pageAttr.Value = System.Convert.ToString(page);
    attrs.Append(pageAttr);

    XmlAttribute countAttr = doc.CreateAttribute("count");
    countAttr.Value = System.Convert.ToString(count);
    attrs.Append(countAttr);

    StringBuilder sb = new StringBuilder();
    StringWriter stringWriter = new StringWriter(sb);

    XmlTextWriter writer = new XmlTextWriter(stringWriter);
    doc.WriteTo(writer);
    writer.Close();

    return sb.ToString();
}

Ensuite le principe reste relativement similaire à celui utilisé pour les QueryExpression :
// Initialisation des informations de pagination
int pageNumber = 1;
string pagingCookie = null;
int count = 250; // 250 enregistrements seront récupérés à chaque lot

// Requête pour récupérer l'intégralité des comptes
string fetchXml = @"<fetch version='1.0' mapping='logical' output-format='xml-platform'>
                        <entity name='account'>
                            <all-attributes />
                        </entity>
                    </fetch>";

EntityCollection result = null;

// Récupère les enregistrements par lot de 250 jusqu'à ce qu'ils soient tous récupérés
do
{
    string xml = CreateFetchXml(fetchXml, pagingCookie, pageNumber, count);
    var query = new FetchExpression(xml);
    result = service.RetrieveMultiple(query); // service est une instance de l'interface IOrganizationService

    // Faire quelque chose avec result

    pagingCookie = result.PagingCookie;
    pageNumber++;
} while (result.MoreRecords);

Sources