TripPin deel 7 - Geavanceerd schema met M-typen
Notitie
Deze inhoud verwijst momenteel naar inhoud uit een verouderde implementatie voor eenheidstests in Visual Studio. De inhoud wordt in de nabije toekomst bijgewerkt om het nieuwe Power Query SDK-testframework te behandelen.
Deze meerdelige zelfstudie bevat informatie over het maken van een nieuwe gegevensbronextensie voor Power Query. De zelfstudie is bedoeld om opeenvolgend te worden uitgevoerd. Elke les bouwt voort 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:
- Een tabelschema afdwingen met M-typen
- Typen instellen voor geneste records en lijsten
- Code herstructureren voor hergebruik en eenheidstests
In de vorige les hebt u uw tabelschema's gedefinieerd met behulp van een eenvoudig systeem voor schematabellen. Deze benadering voor schematabellen werkt voor veel REST API's/Gegevens Verbinding maken ors, maar services die volledige of diep geneste gegevenssets retourneren, kunnen profiteren van de benadering in deze zelfstudie, die gebruikmaakt van het M-typesysteem.
In deze les wordt u begeleid bij de volgende stappen:
- Eenheidstests toevoegen.
- Aangepaste M-typen definiëren.
- Een schema afdwingen met behulp van typen.
- Algemene code herstructureren in afzonderlijke bestanden.
Eenheidstests toevoegen
Voordat u begint met het gebruik van de geavanceerde schemalogica, voegt u een set eenheidstests toe aan uw connector om de kans op onbedoeld breken van iets te verminderen. Eenheidstests werken als volgt:
- Kopieer de algemene code uit het UnitTest-voorbeeld naar uw
TripPin.query.pq
bestand. - Voeg een sectiedeclaratie toe aan het begin van het
TripPin.query.pq
bestand. - Maak een gedeelde record (genaamd
TripPin.UnitTest
). - Definieer een
Fact
voor elke test. - Roep
Facts.Summarize()
aan om alle tests uit te voeren. - Verwijs naar de vorige aanroep als de gedeelde waarde om ervoor te zorgen dat deze wordt geëvalueerd wanneer het project wordt uitgevoerd in 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];
Als u uitvoeren selecteert in het project, worden alle feiten geëvalueerd en krijgt u een rapportuitvoer die er als volgt uitziet:
Met behulp van enkele principes van testgestuurde ontwikkeling voegt u nu een test toe die momenteel mislukt, maar binnenkort opnieuw wordt geïmplementeerd en opgelost (aan het einde van deze zelfstudie). U voegt met name een test toe waarmee een van de geneste records (e-mailberichten) wordt gecontroleerd die u terugkrijgt in de entiteit Mensen.
Fact("Emails is properly typed", type text, Type.ListItem(Value.Type(People{0}[Emails])))
Als u de code opnieuw uitvoert, ziet u nu dat u een mislukte test hebt.
Nu hoeft u alleen de functionaliteit te implementeren om dit werk te laten werken.
Aangepaste M-typen definiëren
De methode voor het afdwingen van schema's in de vorige les heeft 'schematabellen' gebruikt die zijn gedefinieerd als naam-/typeparen. Het werkt goed bij het werken met platgemaakte/relationele gegevens, maar biedt geen ondersteuning voor instellingstypen voor geneste records/tabellen/lijsten, of u kunt typedefinities opnieuw gebruiken in tabellen/entiteiten.
In het geval TripPin bevatten de gegevens in de entiteiten Mensen en Luchthavens gestructureerde kolommen en delen ze zelfs een type (Location
) voor het weergeven van adresgegevens. In plaats van naam-/typeparen in een schematabel te definiëren, definieert u elk van deze entiteiten met aangepaste M-typedeclaraties.
Hier volgt een snelle vernieuwing van typen in de M-taal uit de taalspecificatie:
Een typewaarde is een waarde die andere waarden classificeert . Een waarde die door een type wordt geclassificeerd, wordt gezegd dat deze voldoet aan dat type. Het M-typesysteem bestaat uit de volgende typen:
- Primitieve typen, die primitieve waarden classificeren (
binary
,date
,datetime
,datetimezone
,duration
, , ,null
logical
text
number
list
record
,time
, )type
en ook een aantal abstracte typen (function
,table
,any
en )none
- Recordtypen, waarmee recordwaarden worden geclassificeerd op basis van veldnamen en waardetypen
- Lijsttypen, waarmee lijsten worden geclassificeerd met één itembasistype
- Functietypen, waarmee functiewaarden worden geclassificeerd op basis van de typen van hun parameters en retourwaarden
- Tabeltypen, waarmee tabelwaarden worden geclassificeerd op basis van kolomnamen, kolomtypen en sleutels
- Null-typen, waarmee de waarde null wordt geclassificeerd naast alle waarden die zijn geclassificeerd door een basistype
- Typetypen, waarmee waarden worden geclassificeerd die typen zijn
Met behulp van de onbewerkte JSON-uitvoer die u krijgt (en/of de definities opzoekt in de $metadata van de service), kunt u de volgende recordtypen definiëren om complexe OData-typen weer te geven:
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
];
Let op hoe de LocationType
verwijzingen naar de CityType
gestructureerde kolommen verwijzen en LocType
weergeven.
Voor de entiteiten op het hoogste niveau (die u wilt weergeven als tabellen), definieert u tabeltypen:
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
];
Vervolgens werkt u de SchemaTable
variabele (die u gebruikt als een opzoektabel) bij voor de entiteit om toewijzingen te typen) om deze nieuwe typedefinities te gebruiken:
SchemaTable = #table({"Entity", "Type"}, {
{"Airlines", AirlinesType },
{"Airports", AirportsType },
{"People", PeopleType}
});
Een schema afdwingen met behulp van typen
U bent afhankelijk van een algemene functie (Table.ChangeType
) om een schema af te dwingen voor uw gegevens, net zoals u in de vorige les hebt gebruiktSchemaTransformTable
.
In tegenstelling tot SchemaTransformTable
, Table.ChangeType
wordt een werkelijk M-tabeltype als argument gebruikt en wordt uw schema recursief toegepast voor alle geneste typen. De handtekening ziet er als volgt uit:
Table.ChangeType = (table, tableType as type) as nullable table => ...
De volledige codevermelding voor de Table.ChangeType
functie vindt u in het bestand Table.ChangeType.pqm .
Notitie
Voor flexibiliteit kan de functie worden gebruikt voor tabellen, evenals lijsten met records (zoals tabellen worden weergegeven in een JSON-document).
Vervolgens moet u de connectorcode bijwerken om de schema
parameter van een in een type
table
te wijzigen en een aanroep toe te voegen.Table.ChangeType
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
wordt bijgewerkt om de lijst met velden uit het schema te gebruiken (om te weten wat er moet worden uitgevouwen wanneer u de resultaten krijgt), maar laat de daadwerkelijke schemaafdwinging over aan 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];
Bevestigen dat geneste typen worden ingesteld
De definitie voor uw PeopleType
veld wordt nu ingesteld Emails
op een lijst met tekst ({text}
).
Als u de typen correct toepast, moet de aanroep naar Type.ListItem in uw eenheidstest nu worden geretourneerd type text
in plaats type any
van .
Als u de eenheidstests opnieuw uitvoert, ziet u dat ze nu allemaal worden doorgegeven.
Algemene code herstructureren in afzonderlijke bestanden
Notitie
De M-engine biedt in de toekomst verbeterde ondersteuning voor het verwijzen naar externe modules/gemeenschappelijke code, maar deze aanpak moet u tot die tijd doornemen.
Op dit moment heeft uw extensie bijna zoveel 'algemene' code als TripPin-connectorcode. In de toekomst maken deze algemene functies deel uit van de ingebouwde standaardfunctiebibliotheek of kunt u ernaar verwijzen vanuit een andere extensie. Op dit moment herstructureer u uw code op de volgende manier:
- Verplaats de herbruikbare functies om bestanden (.pqm) te scheiden.
- Stel de eigenschap Build Action voor het bestand in op Compileren om ervoor te zorgen dat deze wordt opgenomen in het extensiebestand tijdens de build.
- Definieer een functie om de code te laden met behulp van Expression.Evaluate.
- Laad elk van de algemene functies die u wilt gebruiken.
De code hiervoor is opgenomen in het onderstaande codefragment:
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");
Conclusie
In deze zelfstudie zijn een aantal verbeteringen aangebracht in de manier waarop u een schema afdwingt voor de gegevens die u uit een REST API krijgt. De connector codeert momenteel de schemagegevens, die tijdens runtime een prestatievoordeel hebben, maar kan zich niet aanpassen aan wijzigingen in de metagegevens van de service. Toekomstige zelfstudies worden verplaatst naar een puur dynamische benadering die het schema afdingt van het $metadata document van de service.
Naast de schemawijzigingen heeft deze zelfstudie moduletests voor uw code toegevoegd en de algemene helperfuncties geherstructureerd in afzonderlijke bestanden om de algehele leesbaarheid te verbeteren.