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:
- Legge til enhetstester.
- Definerer egendefinerte M-typer.
- Fremtving et skjema ved hjelp av typer.
- 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:
- Kopier den vanlige koden fra UnitTest-eksemplet til
TripPin.query.pq
filen. - Legg til en inndelingsdeklarasjon øverst i
TripPin.query.pq
filen. - Opprett en delt post (kalt
TripPin.UnitTest
). - Definer en
Fact
for hver test. - Ring
Facts.Summarize()
for å kjøre alle testene. - 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:
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.
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
,type
time
) og inkluderer også en rekke abstrakte typer (function
,table
,any
ognone
)- 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 SchemaTransformTable
tar 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;
GetPage
oppdateres 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 any
for .
Hvis du kjører enhetstestene på nytt, viser de at alle nå passerer.
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:
- Flytt funksjonene som kan brukes på nytt for å skille filer (PQM).
- Angi bygghandling-egenskapen på filen til Kompiler for å sikre at den blir inkludert i filtypen under bygget.
- Definer en funksjon for å laste inn koden ved hjelp av Expression.Evaluate.
- 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.