Windows Azure Mobile Services Resultset Limit umgehen
Wer mit Windows Azure Mobile Services arbeitet, wird sehr schnell feststellen, dass standardmäßig pro Abfrage immer nur 50 Ergebnisse zurückgegeben werden. Über die explizite Angabe des "Take" Befehls kann ich dieses Resultset auf bis zu 1000 Items erhöhen. Für die meisten Anwendungsfälle ist das sicherlich ausreichend. So liefert beispielsweise die Abfrage
var resList = await App.MobileService.GetTable<TableItem>().Where(w =>
(w.ID == 42)).Take(1000).ToListAsync();
1000 Ergebnisse vom Typen TableItem. Die 1000 ist als oberes Limit gewählt, damit man nicht sinnlos Mobilgeräte mit Daten flutet, die nachher ohnehin niemand mehr durchstöbern mag. Leider ist es in manchen Fällen dennoch nötig noch ein paar Daten mehr zu erhalten. Das ist zum Beispiel der Fall, wenn man sich einen eigenen Filter schreibt und dafür IServiceFilter implementiert, um serverseitig die zurückgelieferten Daten zu filtern. Das Problem ist dann, dass der Filter zuschlägt, nachdem das Limit auf 1000 Items gegriffen hat. Im Extremfall könnte das bedeuten, dass man eine Query mit Filter gegen einen Datenbestand abfeuert, die eigentlich Treffer hätte - durch das vorgezogene Limit werden aber nur Datensätze ausgewählt, die der Filter aussortiert - und letztlich bekommt man überhaupt keine Daten zurückgeliefert. Genau den beschriebenen Fall hatte ich selbst erlebt und mir dann mit einem kleinen Kniff beholfen, den ich Euch hier zeige. (Vielleicht muss man auch gar nicht so weit gehen - eventuell möchte man ja einfach tatsächlich mehr als 1000 Ergebnisse anzeigen.)
Im Prinzip ist das Vorgehen logisch: Wenn ich pro Abfrage nur 1000 Ergebnisse kriegen kann, dann muss ich eben mehr als einmal abfragen. Hier mein Code dafür:
public async Task<IEnumerable<object>> GetAllItemsWithoutLimitAsync
(GetAllItemsRecursiveAsyncDelegate argDelegate,
string[] argPara = null) { // returned IEnum IEnumerable<object> retlist = new List<object>(); // count how many items should be skipped int skipCounter = 0; // true, if new items have been found bool foundNew = false; // loop until no new items have been found while (skipCounter == 0 || foundNew) { // search new items using the query delegate
// and pass parameters, if required var newList = await argDelegate(skipCounter, argPara); // if new items have been found... if (newList.Count() > 0) { // ... union with existing list, to avoid duplicates retlist = retlist.Union(newList); // update bool & counter foundNew = true; skipCounter += 1000; } else { // set stop marker foundNew = false; } } return retlist; }
Ich hab den Code durchgehend kommentiert, und denke, dass die Kommentare eigentlich alles erklären. Die wichtige Frage ist jetzt, wie dieser Delegate aussehen muss, den ich hier übergebe.
public delegate Task<IEnumerable<object>> GetAllItemsRecursiveAsyncDelegate
(int argSkipCount, string[] argPara);
Ich hab den Delegaten so aufgebaut, dass er in der Lage ist, noch ein paar Parameter zu übernehmen, die ich später auswerten kann. Eine Implementierung des Delegaten könnte zum Beispiel so aussehen:
async public Task<IEnumerable<object>> GetTrackedLocationsForSessionsAsync
(int argSkipCount, string[] argPara) { // if no parameters have been provided return an empty list if (argPara[0] == null) { return new List<object>(); } // else query the mobile service var resList = await App.MobileService.GetTable<Item>().
Where(w => (w.Name == argPara[0])).Skip(argSkipCount)
.Take(1000).ToListAsync(); // return the result return (IEnumerable<object>)resList; }
Wir übergeben hier also jeweils, wie viele Items uns nicht interessieren und beziehen dann die nächsten 1000 Items. Wenn Ihr vor der gleichen Aufgabe steht, hilft Euch das vielleicht etwas weiter. Sorry für die blöden Zeilenumbrüche…. sie sind dem Bloglayout geschuldet.