TripPin deel 5 - Paginering

Deze meerdelige zelfstudie gaat over het maken van een nieuwe gegevensbronextensie voor Power Query. De zelfstudie is bedoeld om opeenvolgend te worden uitgevoerd elke les is gebaseerd op de connector die in de vorige lessen is gemaakt, en voegt incrementeel nieuwe mogelijkheden toe — aan uw connector.

In deze les gaat u het volgende doen:

  • Ondersteuning voor paginering toevoegen aan de connector

Veel REST API's retourneren gegevens op 'pagina's', waardoor clients meerdere aanvragen moeten indienen om de resultaten samen te voegen. Hoewel er enkele algemene conventies voor paginering zijn (zoals RFC 5988),varieert het over het algemeen van API naar API. Gelukkig is TripPin een OData-service en de OData-standaard definieert een manier van paginering met behulp van odata.nextLink-waarden die worden geretourneerd in de body van het antwoord.

Om eerdere iteraties van de connector te vereenvoudigen, TripPin.Feed was de functie niet paginabewust. Het parseerde gewoon de JSON die door de aanvraag werd geretourneerd en formatteer deze als een tabel. Degenen die bekend zijn met het OData-protocol hebben mogelijk opgemerkt dat er een aantal onjuiste veronderstellingen zijn gedaan over de indeling van het antwoord (bijvoorbeeld ervan uitgaande dat er een veld is met een matrix van value records).

In deze les gaat u uw reactieverwerkingslogica verbeteren door deze paginabewust te maken. In toekomstige zelfstudies wordt de paginaverwerkingslogica robuuster en kunnen meerdere antwoordindelingen (inclusief fouten van de service) worden verwerkt.

Notitie

U hoeft uw eigen pagineringslogica niet te implementeren met connectors op basis van OData.Feed,omdat dit alles automatisch voor u verwerkt.

Controlelijst voor paginering

Wanneer u ondersteuning voor paginering implementeert, moet u de volgende dingen over uw API weten:

  • Hoe vraagt u de volgende pagina met gegevens aan?
  • Berekent het pagineringsmechanisme waarden of extraheert u de URL voor de volgende pagina uit het antwoord?
  • Hoe weet u wanneer u moet stoppen met pagineren?
  • Zijn er parameters met betrekking tot paginering die u moet kennen? (zoals 'paginaformaat')

Het antwoord op deze vragen is van invloed op de manier waarop u uw pagineringslogica implementeert. Hoewel er een bepaalde hoeveelheid code opnieuw moet worden gebruikt voor paginerings-implementaties (zoals het gebruik van Table.GenerateByPage,is voor de meeste connectors uiteindelijk aangepaste logica vereist.

Notitie

Deze les bevat pagineringslogica voor een OData-service, die een specifieke indeling volgt. Raadpleeg de documentatie voor uw API om te bepalen welke wijzigingen u in uw connector moet aanbrengen om de pagineringsindeling te ondersteunen.

Overzicht van OData-paginering

OData-paginering wordt aangestuurd door nextLink-aantekeningen in de nettolading van het antwoord. De waarde nextLink bevat de URL naar de volgende pagina met gegevens. U weet of er nog een pagina met gegevens is door te zoeken naar een odata.nextLink veld in het buitenste object in het antwoord. Als er geen veld odata.nextLink is, hebt u al uw gegevens gelezen.

{
  "odata.context": "...",
  "odata.count": 37,
  "value": [
    { },
    { },
    { }
  ],
  "odata.nextLink": "...?$skiptoken=342r89"
}

Sommige OData-services bieden clients de mogelijkheid om een voorkeur voor maximale paginagrootte op te geven,maar het is aan de service om deze te eren. Power Query moeten reacties van elke grootte kunnen verwerken, zodat u zich geen zorgen hoeft te maken over het opgeven van een voorkeur voor paginagrootte die u kunt ondersteunen, ongeacht wat de service voor u — heeft.

Meer informatie over servergestuurde paginering vindt u in de OData-specificatie.

TripPin testen

Bevestig het huidige gedrag van de extensie uit de vorige zelfstudie voordat u de paginerings-implementatie verhelpt. De volgende testquery haalt de tabel People op en voegt een indexkolom toe om het huidige aantal rijen weer te geven.

let
    source = TripPin.Contents(),
    data = source{[Name="People"]}[Data],
    withRowCount = Table.AddIndexColumn(data, "Index")
in
    withRowCount

Schakel Fiddler in en voer de query uit in Visual Studio. U ziet dat de query een tabel met 8 rijen retourneert (index 0 tot en met 7).

QueryWithoutPaging.

Als u de body van het antwoord van Fiddler bekijkt, ziet u dat deze in feite een veld bevat, waarmee wordt aangegeven dat er meer pagina's met @odata.nextLink gegevens beschikbaar zijn.

{
  "@odata.context": "https://services.odata.org/V4/TripPinService/$metadata#People",
  "@odata.nextLink": "https://services.odata.org/v4/TripPinService/People?%24skiptoken=8",
  "value": [
    { },
    { },
    { }
  ]
}

Paginering voor TripPin implementeren

U gaat nu de volgende wijzigingen aanbrengen in uw extensie:

  1. De algemene functie Table.GenerateByPage importeren
  2. Een functie GetAllPagesByNextLink toevoegen die gebruikmaakt van Table.GenerateByPage om alle pagina's samen te plakken
  3. Een functie GetPage toevoegen die één pagina met gegevens kan lezen
  4. Een functie GetNextLink toevoegen om de volgende URL uit het antwoord te extraheren
  5. Bijwerken TripPin.Feed voor het gebruik van de nieuwe functies voor paginalezers

Notitie

Zoals eerder in deze zelfstudie is vermeld, verschilt de pagineringslogica per gegevensbron. De implementatie hier probeert de logica op te breken in functies die herbruikbaar moeten zijn voor bronnen die gebruikmaken van volgende koppelingen die in het antwoord worden geretourneerd.

Table.GenerateByPage

De Table.GenerateByPage functie kan worden gebruikt om efficiënt meerdere pagina's met gegevens in één tabel te combineren. Dit doet u door herhaaldelijk de functie aan te roepen die is doorgegeven als getNextPage de parameter , totdat deze een null ontvangt. De functieparameter moet één argument hebben en een nullable table retourneren.

getNextPage = (lastPage) as nullable table => ...

Elke aanroep getNextPage van ontvangt de uitvoer van de vorige aanroep.

// The getNextPage function takes a single argument and is expected to return a nullable table
Table.GenerateByPage = (getNextPage as function) as table =>
    let        
        listOfPages = List.Generate(
            () => getNextPage(null),            // get the first page of data
            (lastPage) => lastPage <> null,     // stop when the function returns null
            (lastPage) => getNextPage(lastPage) // pass the previous page to the next function call
        ),
        // concatenate the pages together
        tableOfPages = Table.FromList(listOfPages, Splitter.SplitByNothing(), {"Column1"}),
        firstRow = tableOfPages{0}?
    in
        // if we didn't get back any pages of data, return an empty table
        // otherwise set the table type based on the columns of the first page
        if (firstRow = null) then
            Table.FromRows({})
        else        
            Value.ReplaceType(
                Table.ExpandTableColumn(tableOfPages, "Column1", Table.ColumnNames(firstRow[Column1])),
                Value.Type(firstRow[Column1])
            );

Enkele opmerkingen over Table.GenerateByPage :

  • De functie moet de URL van de volgende pagina ophalen (of het paginanummer, of andere waarden die worden gebruikt getNextPage om de pagineringslogica te implementeren). Dit wordt meestal gedaan door waarden aan meta de pagina toe te voegen voordat deze worden terugsturen.
  • De kolommen en het tabeltype van de gecombineerde tabel (dat wil zeggen alle pagina's samen) worden afgeleid van de eerste pagina met gegevens. De getNextPage functie moet elke pagina met gegevens normaliseren.
  • De eerste aanroep van getNextPage ontvangt een null-parameter.
  • getNextPage moet null retourneren wanneer er geen pagina's meer zijn.

De hoofdtitel van uw GetAllPagesByNextLink functie implementeert het getNextPage functieargument voor Table.GenerateByPage . Hiermee wordt de functie aanroepen en de URL opgehaald voor de volgende pagina met gegevens uit het veld van de GetPage record uit de vorige NextLink meta aanroep.

// Read all pages of data.
// After every page, we check the "NextLink" record on the metadata of the previous request.
// Table.GenerateByPage will keep asking for more pages until we return null.
GetAllPagesByNextLink = (url as text) as table =>
    Table.GenerateByPage((previous) => 
        let
            // if previous is null, then this is our first page of data
            nextLink = if (previous = null) then url else Value.Metadata(previous)[NextLink]?,
            // if NextLink was set to null by the previous call, we know we have no more data
            page = if (nextLink <> null) then GetPage(nextLink) else null
        in
            page
    );

GetPage implementeren

Uw functie gebruikt Web.Contents om één pagina met gegevens op te halen uit de GetPage TripPin-service en het antwoord te converteren naar een tabel. De functie geeft het antwoord van Web.Contents door aan de functie om de URL van de volgende pagina te extraheren en stelt deze in op de record van de geretourneerde tabel GetNextLink meta (pagina met gegevens).

Deze implementatie is een enigszins gewijzigde versie van de TripPin.Feed aanroep uit de vorige zelfstudies.

GetPage = (url as text) as table =>
    let
        response = Web.Contents(url, [ Headers = DefaultRequestHeaders ]),        
        body = Json.Document(response),
        nextLink = GetNextLink(body),
        data = Table.FromRecords(body[value])
    in
        data meta [NextLink = nextLink];

Uw GetNextLink functie controleert eenvoudigweg de hoofdwaarde van het antwoord op een @odata.nextLink veld en retourneert de waarde.

// In this implementation, 'response' will be the parsed body of the response after the call to Json.Document.
// Look for the '@odata.nextLink' field and simply return null if it doesn't exist.
GetNextLink = (response) as nullable text => Record.FieldOrDefault(response, "@odata.nextLink");

Alles samenbrengen

De laatste stap voor het implementeren van uw pagineringslogica is het bijwerken TripPin.Feed van de nieuwe functies. Op dit moment roept u gewoon door naar , maar in volgende zelfstudies voegt u nieuwe mogelijkheden toe (zoals het afdwingen van een schema en GetAllPagesByNextLink queryparameterlogica).

TripPin.Feed = (url as text) as table => GetAllPagesByNextLink(url);

Als u dezelfde testquery van eerder in de zelfstudie opnieuw uitvoert, ziet u nu de paginalezer in actie. U ziet ook dat er 20 rijen in het antwoord staan in plaats van 8.

QueryWithPaging.

Als u de aanvragen in Fiddler bekijkt, ziet u nu afzonderlijke aanvragen voor elke pagina met gegevens.

Fiddler.

Notitie

U ziet dubbele aanvragen voor de eerste pagina met gegevens van de service, wat niet ideaal is. De extra aanvraag is het resultaat van het gedrag van de schemacontrole van de M-engine. Negeer dit probleem voor nu en los het op in de volgende zelfstudie,waarin u een explict schema gaat toepassen.

Conclusie

In deze les hebt u geleerd hoe u pagineringsondersteuning voor een REST API implementeert. Hoewel de logica waarschijnlijk verschilt tussen API's, moet het hier tot stand gebrachte patroon opnieuw kunnen worden gebruikt met kleine wijzigingen.

In de volgende les bekijkt u hoe u een expliciet schema op uw gegevens kunt toepassen, verder dan de eenvoudige en text number gegevenstypen die u krijgt van Json.Document .

Volgende stappen

TripPin deel 6 - Schema