Delen via


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:

  1. Eenheidstests toevoegen.
  2. Aangepaste M-typen definiëren.
  3. Een schema afdwingen met behulp van typen.
  4. 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:

  1. Kopieer de algemene code uit het UnitTest-voorbeeld naar uw TripPin.query.pq bestand.
  2. Voeg een sectiedeclaratie toe aan het begin van het TripPin.query.pq bestand.
  3. Maak een gedeelde record (genaamd TripPin.UnitTest).
  4. Definieer een Fact voor elke test.
  5. Roep Facts.Summarize() aan om alle tests uit te voeren.
  6. 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:

Eerste eenheidstest.

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.

Eenheidstest met fout.

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, , , nulllogicaltextnumberlistrecord, time, ) typeen ook een aantal abstracte typen (function, table, anyen )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 typetable te wijzigen en een aanroep toe te voegen.Table.ChangeTypeGetEntity

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 anyvan .

Als u de eenheidstests opnieuw uitvoert, ziet u dat ze nu allemaal worden doorgegeven.

Eenheidstest met succes.

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:

  1. Verplaats de herbruikbare functies om bestanden (.pqm) te scheiden.
  2. 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.
  3. Definieer een functie om de code te laden met behulp van Expression.Evaluate.
  4. 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.

Volgende stappen

TripPin deel 8 - Diagnostische gegevens toevoegen