Del via


TripPin del 7 – Avansert skjema med M-typer

Merk

Dette innholdet refererer for øyeblikket til innhold fra en eldre implementering for enhetstesting i Visual Studio. Innholdet oppdateres i nær fremtid for å dekke det nye Power Query SDK-testrammeverket.

Denne flerdelte opplæringen dekker opprettelsen av en ny datakildeutvidelse for Power Query. Opplæringen er ment å gjøres sekvensielt – hver leksjon bygger på koblingen som er opprettet i tidligere leksjoner, og legger trinnvis til nye funksjoner i koblingen.

I denne leksjonen vil du:

  • Fremtving et tabellskjema ved hjelp av M-typer
  • Angi typer for nestede poster og lister
  • Refaktorkode for gjenbruk og enhetstesting

I den forrige leksjonen definerte du tabellskjemaene ved hjelp av et enkelt skjematabellsystem. Denne skjematabelltilnærmingen fungerer for mange REST-API-er/data Koble til orer, men tjenester som returnerer fullstendige eller dypt nestede datasett, kan dra nytte av tilnærmingen i denne opplæringen, som utnytter M-typesystemet.

Denne leksjonen veileder deg gjennom følgende trinn:

  1. Legge til enhetstester.
  2. Definerer egendefinerte M-typer.
  3. Fremtving et skjema ved hjelp av typer.
  4. Refaktorerer felles kode i separate filer.

Legge til enhetstester

Før du begynner å bruke den avanserte skjemalogikken, legger du til et sett med enhetstester i koblingen for å redusere sjansen for utilsiktet å bryte noe. Enhetstesting fungerer slik:

  1. Kopier den vanlige koden fra UnitTest-eksemplet til TripPin.query.pq filen.
  2. Legg til en inndelingsdeklarasjon øverst i TripPin.query.pq filen.
  3. Opprett en delt post (kalt TripPin.UnitTest).
  4. Definer en Fact for hver test.
  5. Ring Facts.Summarize() for å kjøre alle testene.
  6. Referer det forrige kallet som den delte verdien for å sikre at det evalueres når prosjektet kjøres i Visual Studio.
section TripPinUnitTests;

shared TripPin.UnitTest =
[
    // Put any common variables here if you only want them to be evaluated once
    RootTable = TripPin.Contents(),
    Airlines = RootTable{[Name="Airlines"]}[Data],
    Airports = RootTable{[Name="Airports"]}[Data],
    People = RootTable{[Name="People"]}[Data],

    // Fact(<Name of the Test>, <Expected Value>, <Actual Value>)
    // <Expected Value> and <Actual Value> can be a literal or let statement
    facts =
    {
        Fact("Check that we have three entries in our nav table", 3, Table.RowCount(RootTable)),
        Fact("We have Airline data?", true, not Table.IsEmpty(Airlines)),
        Fact("We have People data?", true, not Table.IsEmpty(People)),
        Fact("We have Airport data?", true, not Table.IsEmpty(Airports)),
        Fact("Airlines only has 2 columns", 2, List.Count(Table.ColumnNames(Airlines))),        
        Fact("Airline table has the right fields",
            {"AirlineCode","Name"},
            Record.FieldNames(Type.RecordFields(Type.TableRow(Value.Type(Airlines))))
        )
    },

    report = Facts.Summarize(facts)
][report];

Hvis du velger kjør på prosjektet, evalueres alle fakta, og du får en rapportutdata som ser slik ut:

Første enhetstest.

Når du bruker noen prinsipper fra testdrevet utvikling, legger du nå til en test som for øyeblikket mislykkes, men som snart blir gjeninnført og løst (ved slutten av denne opplæringen). Spesielt legger du til en test som kontrollerer en av de nestede postene (e-postmeldinger) du får tilbake i Folk-enheten.

Fact("Emails is properly typed", type text, Type.ListItem(Value.Type(People{0}[Emails])))

Hvis du kjører koden på nytt, skal du nå se at du har en mislykket test.

Enhetstest med feil.

Nå trenger du bare å implementere funksjonaliteten for å få dette til å fungere.

Definere egendefinerte M-typer

Skjemahåndhevelsestilnærmingen i den forrige leksjonen brukte «skjematabeller» definert som navn/type-par. Det fungerer bra når du arbeider med flate/relasjonelle data, men støtter ikke innstillingstyper på nestede poster/tabeller/lister, eller lar deg bruke typedefinisjoner på nytt på tvers av tabeller/enheter.

I TripPin-saken inneholder dataene i enhetene Folk og flyplasser strukturerte kolonner, og deler til og med en type (Location) for å representere adresseinformasjon. I stedet for å definere navn/type-par i en skjematabell, definerer du hver av disse enhetene ved hjelp av egendefinerte M-typedeklarasjoner.

Her er en rask oppdatering om typer i M-språket fra språkspesifikasjonen:

En typeverdi er en verdi som klassifiserer andre verdier. En verdi som klassifiseres etter en type, sies å samsvare med denne typen. M-typesystemet består av følgende typer typer:

  • Primitive typer, som klassifiserer primitive verdier (binary, , date, datetime, datetimezone, duration, list, logical, null, number, record, text, typetime) og inkluderer også en rekke abstrakte typer (function, table, anyog none)
  • Oppføringstyper som klassifiserer postverdier basert på feltnavn og verdityper
  • Listetyper som klassifiserer lister ved hjelp av en basistype for enkeltelement
  • Funksjonstyper som klassifiserer funksjonsverdier basert på typene parametere og returverdier
  • Tabelltyper som klassifiserer tabellverdier basert på kolonnenavn, kolonnetyper og nøkler
  • Nullverdityper, som klassifiserer verdien null i tillegg til alle verdiene klassifisert etter en basistype
  • Typetyper som klassifiserer verdier som er typer

Ved hjelp av de rå JSON-utdataene du får (og/eller slå opp definisjonene i tjenestens $metadata), kan du definere følgende posttyper for å representere komplekse OData-typer:

LocationType = type [
    Address = text,
    City = CityType,
    Loc = LocType
];

CityType = type [
    CountryRegion = text,
    Name = text,
    Region = text
];

LocType = type [
    #"type" = text,
    coordinates = {number},
    crs = CrsType
];

CrsType = type [
    #"type" = text,
    properties = record
];

Legg merke til LocationType hvordan referansene CityType refererer til og LocType representerer de strukturerte kolonnene.

For enheter på øverste nivå (som du vil representere som tabeller), definerer du tabelltyper:

AirlinesType = type table [
    AirlineCode = text,
    Name = text
];

AirportsType = type table [
    Name = text,
    IataCode = text,
    Location = LocationType
];

PeopleType = type table [
    UserName = text,
    FirstName = text,
    LastName = text,
    Emails = {text},
    AddressInfo = {nullable LocationType},
    Gender = nullable text,
    Concurrency = Int64.Type
];

Deretter oppdaterer du variabelen SchemaTable (som du bruker som en "oppslagstabell" for enhet til å skrive tilordninger) for å bruke disse nye typedefinisjonene:

SchemaTable = #table({"Entity", "Type"}, {
    {"Airlines", AirlinesType },    
    {"Airports", AirportsType },
    {"People", PeopleType}    
});

Fremtvinge et skjema ved hjelp av typer

Du vil være avhengig av en felles funksjon (Table.ChangeType) for å fremtvinge et skjema på dataene, omtrent som du brukte SchemaTransformTable i forrige leksjon. I motsetning til SchemaTransformTabletar Table.ChangeType det inn en faktisk M-tabelltype som et argument, og vil bruke skjemaet rekursivt for alle nestede typer. Signaturen ser slik ut:

Table.ChangeType = (table, tableType as type) as nullable table => ...

Du finner den fullstendige kodeoppføringen Table.ChangeType for funksjonen i Table.ChangeType.pqm-filen .

Merk

For fleksibilitet kan funksjonen brukes i tabeller, i tillegg til lister over poster (som er hvordan tabeller vil bli representert i et JSON-dokument).

Deretter må du oppdatere koblingskoden for å endre parameteren schema fra en table til en type, og legge til Table.ChangeType et anrop i GetEntity.

GetEntity = (url as text, entity as text) as table => 
    let
        fullUrl = Uri.Combine(url, entity),
        schema = GetSchemaForEntity(entity),
        result = TripPin.Feed(fullUrl, schema),
        appliedSchema = Table.ChangeType(result, schema)
    in
        appliedSchema;

GetPageoppdateres til å bruke listen over felter fra skjemaet (for å vite navnene på hva du skal utvide når du får resultatene), men lar den faktiske skjemahåndhevelse være .GetEntity

GetPage = (url as text, optional schema as type) as table =>
    let
        response = Web.Contents(url, [ Headers = DefaultRequestHeaders ]),        
        body = Json.Document(response),
        nextLink = GetNextLink(body),
        
        // If we have no schema, use Table.FromRecords() instead
        // (and hope that our results all have the same fields).
        // If we have a schema, expand the record using its field names
        data =
            if (schema <> null) then
                Table.FromRecords(body[value])
            else
                let
                    // convert the list of records into a table (single column of records)
                    asTable = Table.FromList(body[value], Splitter.SplitByNothing(), {"Column1"}),
                    fields = Record.FieldNames(Type.RecordFields(Type.TableRow(schema))),
                    expanded = Table.ExpandRecordColumn(asTable, fields)
                in
                    expanded
    in
        data meta [NextLink = nextLink];

Bekrefter at nestede typer angis

Definisjonen for ditt PeopleType nå angir Emails feltet til en liste med tekst ({text}). Hvis du bruker typene riktig, skal kallet til Type.ListItem i enhetstesten nå returneres type text i stedet type anyfor .

Hvis du kjører enhetstestene på nytt, viser de at alle nå passerer.

Enhetstest med suksess.

Refaktorerer felles kode i separate filer

Merk

M-motoren vil ha forbedret støtte for å referere til eksterne moduler/felles kode i fremtiden, men denne tilnærmingen bør føre deg gjennom til da.

På dette tidspunktet har utvidelsen nesten like mye "vanlig" kode som TripPin-koblingskode. I fremtiden vil disse vanlige funksjonene enten være en del av det innebygde standard funksjonsbiblioteket, eller du vil kunne referere dem fra en annen utvidelse. Foreløpig refaktorerer du koden på følgende måte:

  1. Flytt funksjonene som kan brukes på nytt for å skille filer (PQM).
  2. Angi bygghandling-egenskapen på filen til Kompiler for å sikre at den blir inkludert i filtypen under bygget.
  3. Definer en funksjon for å laste inn koden ved hjelp av Expression.Evaluate.
  4. Last inn hver av de vanlige funksjonene du vil bruke.

Koden for å gjøre dette er inkludert i snutten nedenfor:

Extension.LoadFunction = (fileName as text) =>
  let
      binary = Extension.Contents(fileName),
      asText = Text.FromBinary(binary)
  in
      try
        Expression.Evaluate(asText, #shared)
      catch (e) =>
        error [
            Reason = "Extension.LoadFunction Failure",
            Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
            Message.Parameters = {fileName, e[Reason], e[Message]},
            Detail = [File = fileName, Error = e]
        ];

Table.ChangeType = Extension.LoadFunction("Table.ChangeType.pqm");
Table.GenerateByPage = Extension.LoadFunction("Table.GenerateByPage.pqm");
Table.ToNavigationTable = Extension.LoadFunction("Table.ToNavigationTable.pqm");

Konklusjon

Denne opplæringen har gjort en rekke forbedringer i måten du fremtvinger et skjema på dataene du får fra en REST-API. Koblingen er for øyeblikket hard koding av skjemainformasjonen, som har en ytelsesfordel ved kjøring, men kan ikke tilpasse seg endringer i tjenestens metadata overtid. Fremtidige opplæringer flyttes til en rent dynamisk tilnærming som vil utlede skjemaet fra tjenestens $metadata dokument.

I tillegg til skjemaendringene, la denne opplæringen til enhetstester for koden din, og refaktorerte de vanlige hjelpefunksjonene i separate filer for å forbedre den generelle lesbarheten.

Neste trinn

TripPin Del 8 – Legge til diagnostikk