Mit mehr als 5 000 Datensätzen in Dynamics 365 arbeiten
QueryExpression oder FetchXML sind zwei der verbreitetsten Arten, Daten in C# aus Dynamics 365 abzurufen – und so praktisch!
Wenn aber mehr als 5 000 Datensätze vorhanden sind, und das ist bei einigen Organisationen der Fall, muss man zusätzlich zu einer simplen QueryExpression oder FetchXML Paging verwenden.
Standardmäßig kann ein Organisationsservice nur die ersten 5 000 Datensätze auswerfen und Details für das nächste Datensetz pagen, falls diese existiert.
Als ich versucht habe, die Methode Art zu finden, alle diese Datensätze durch Paging abzurufen, waren die meisten Beispiele, die ich gefunden habe, für FetchXML. Deshalb werde ich in diesem Post QueryExpression verwenden, nicht nur um die Datensätze zu extrahieren, sondern auch um mit ihnen zu arbeiten.
Zunächst erstellen wir eine einfache QueryExpression, um alle Datensätze in Dynamics 365 zu extrahieren:
QueryExpression query = new QueryExpression("account");
query.ColumnSet = new ColumnSet("name");
Für weniger als 5 000 Datensätze würde man Folgendes verwenden:
var accountCollection = service.RetrieveMultiple(query);
Wie extrahieren wir nun mehr als 5 000 Datensätze?
Paging for the win! Wo findet man dieses Paging? In der Abfrageantwort in PageInfo. Wir müssen dieses Paging-Cookie aus der Abfrageantwort extrahieren, um es an unsere nächste Page-Anfrage zu senden. Neben dem Paging-Cookie erhalten wir in der Abfrageantwort auch More Records, was eine boolesche Variable ist, die uns sagt, ob es mehr Datensätze zu holen gibt.
Das bringt “wahr” oder “falsch”, was uns hilft zu entschieden, ob wir die letzte Seite erreicht haben oder ob es noch Datensätze gibt.
QueryExpression query = new QueryExpression("account");
query.ColumnSet = new ColumnSet("name");
var completeAccountCollection = RetrieveAllRecords(service, query);
public static List<Entity> RetrieveAllRecords(IOrganizationService service,QueryExpression query)
{
var pageNumber = 1;
var pagingCookie = string.Empty;
var result = new List<Entity>();
EntityCollection resp;
do
{
if (pageNumber != 1)
{
query.PageInfo.PageNumber = pageNumber;
query.PageInfo.PagingCookie = pagingCookie;
}
resp = service.RetrieveMultiple(query);
if (resp.MoreRecords)
{
pageNumber++;
pagingCookie = resp.PagingCookie;
}
//Add the result from RetrieveMultiple to the List to be returned.
result.AddRange(resp.Entities);
}
while (resp.MoreRecords);
return result;
}
Hier verwende ich eine do-while loop, um Datensätze abzufragen bis die Abfrageantwort MoreRecords ist wahr, d.h. es existieren noch Datensätze auf der nächsten Seite, ergibt.
Der Code oben wird uns alle Datensätze in der Dynamics-365-Organisation ausspucken. Aber in den meisten Fällen wird man auch was mit den abgerufenen Datensätzen anfangen wollen. Lasst uns anschauen, wie wir das tun können.
Mit abgerufenen Datensätzen arbeiten
Sagen wir, wir möchten ein Feld in allen Datensätzen aktualisieren. Wenn man in Dynamics 365 Datensätze massenhaft aktualisieren, löschen oder erstellen will, verwendet man ExecuteMultipleRequest. Da Execute OrganizationRequest als Parameteter verwendet, nehmen wir UpdateRequest, um die Aktualisierung durchzuführen. Wir könnten auch service.Update() verwenden, aber Massenvorgänge mit service.Execute() sind performanter und schneller.
List<UpdateRequest> requests = completeAccountCollection.Select((entity) =>
{
Entity myEntity = new Entity("account");
myEntity.Id = entity.Id;
myEntity["status"] = new OptionSetValue(0);
//Statt hier zu aktualisieren, schicken wir einen UpdateRequest zurück, um ihn in Execute zu verwenden.
//service.Update(myEntity);
return new UpdateRequest { Target = myEntity };
}).ToList();
Der Code unten führt die Daten nicht nur massenmäßig aus, sondern liefert auch einen Fortschrittsbericht, wo wir uns im Prozess befinden:
//Es gibt ein Limit, wie viele Anfragen man zu einer Anfragensammlung hinzufügen kann. Wenn dieses Limit überschritten wurde, gibt es eine Fehlermeldung bevor die erste Anfrage überhaupt ausgeführt wird.
//Typisch ist ein Limit von 1 000 Anfragen, aber in der Praxis hängt das von der Verarbeitungszeit des CRM-Servers ab.
// Wir teilen die Anfragenliste auf eine untergeordnete Liste auf.
//Die Größe des Batchs spezifizieren , d.h. hier 30 (um auf Nummer sicher zu gehen)
var chunkSize = 30;
var chunks = SplitList(requests, chunkSize).ToList();
chunks
.Select((mychunk, index) => (mychunk, index: index + 1)).ToList()
.ForEach(x =>
{
//Erstelle eine leere Organization Request Collection.
var orc = new OrganizationRequestCollection();
//Füge den UpdateRequest für jede Entität der Org Request Collection hinzu.
orc.AddRange(x.mychunk);
var emRequest = new ExecuteMultipleRequest()
{
//Weise Einstellungen hinzu, die Execution behavior definieren: continue on error, return responses.
Settings = new ExecuteMultipleSettings() { ContinueOnError = false, ReturnResponses = true },
Requests = orc
};
try
{
//Führe alle Anfragen in der Anfragensammlung aus, indem du eine einzigen Web Method Call verwendest
ExecuteMultipleResponse response = (ExecuteMultipleResponse)service.Execute(emRequest);
//Der Code unten ist für die Nachverfolgung des Fortschritts chunk for chunk.
if (response.IsFaulted == true)
{
foreach (var myresp in response.Responses)
Console.WriteLine($"Error in processing chunk {x.index} .Error : {myresp.Fault.Message}");
}
else
Console.WriteLine($"\rUpdated chunk {x.index} out of {chunks.Count}.");
}
catch (Exception ex)
{
Console.WriteLine($"Exception Occured in chunk {x.index} chunk {ex.ToString()}.");
}
});
/// <Zusammenfassung>
/// Funktion um die Liste in eine untergeordnete Liste aufzuteilen
/// </Zusammenfassung>
public static IEnumerable<List<T>> SplitList<T>(List<T> collection, int nSize = 30)
{
for (int i = 0; i < collection.Count; i += nSize)
yield return collection.GetRange(i, Math.Min(nSize, collection.Count - i));
}
Wie beim Update Request, können Sie DeleteRequest und CreateRequests nach Belieben erstellen.
Ich hoffe, dieser Code hilft dir, wenn du das nächste Mal Probleme mit riesigen Dynamics-Daten hast!