TripPin časť 7 – rozšírená schéma s typmi jazyka M

Tento kurz s viacerými časťami sa zaoberá vytváraním nového rozšírenia zdroja údajov pre Power Query. Tento kurz sa má uskutočniť postupne – každá lekcia vychádza z konektora vytvoreného v predchádzajúcich lekciách a postupne pridáva nové možnosti do konektora.

V tejto lekcii:

  • Vynútenie schémy tabuľky pomocou typov jazyka M
  • Nastavenie typov pre vnorené záznamy a zoznamy
  • Refaktor kódu na opätovné použitie a testovanie zariadenia

V predchádzajúcej lekcii ste definovali schémy tabuľky pomocou jednoduchého systému "Tabuľka schém". Tento prístup tabuľky schém funguje v mnohých rozhraniach REST API alebo Pripojenie údajov, ale služby, ktoré vracajú úplné alebo hlboko vnorené množiny údajov, môžu využívať výhody prístupu v tomto kurze, ktorý využíva systém typov jazyka M.

Táto lekcia vás prevedie nasledujúcimi krokmi:

  1. Pridanie testov zariadenia.
  2. Definovanie vlastných typov jazyka M.
  3. Vynucovanie schémy pomocou typov.
  4. Refaktorovanie bežného kódu do samostatných súborov.

Pridanie testov zariadenia

Skôr než začnete používať pokročilú logiku schémy, pridáte do konektora skupinu testov zariadenia, aby sa zmenšila pravdepodobnosť neúmyselného prelomenia. Testovanie jednotky funguje takto:

  1. Skopírujte spoločný kód zo vzorky UnitTest do súboru TripPin.query.pq .
  2. Do hornej časti súboru pridajte deklaráciu sekcie TripPin.query.pq .
  3. Vytvorenie zdieľaného záznamu (s názvom TripPin.UnitTest).
  4. Definujte hodnotu Fact pre každý test.
  5. Zavolajte príkaz Facts.Summarize() na spustenie všetkých testov.
  6. Odkazujte na predchádzajúce volanie ako na zdieľanú hodnotu, aby ste sa uistili, že sa vyhodnotí pri spustení projektu vo Visual Studiu.
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];

Výberom položky Spustiť v projekte sa vyhodnotia všetky fakty a poskytne vám výstup zostavy, ktorý bude vyzerať takto:

Initial Unit Test.

Pomocou niektorých princípov z vývoja založeného na teste pridáte test, ktorý momentálne zlyhá, ale čoskoro bude opäť zadaný a fixný (do konca tohto kurzu). Konkrétne pridáte test, ktorý kontroluje, či sa niektorý z vnorených záznamov (E-mail) nachádza v entite Ľudia.

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

Ak kód spustíte znova, malo by sa vám teraz zobraziť, že ide o neúspešný test.

Unit test with failure.

Teraz je len potrebné implementovať funkciu, aby táto funkcia fungovala.

Definovanie vlastných typov jazyka M

Prístup uplatnenia schémy v predchádzajúcej lekcii používal "tabuľky schémy" definované ako páry názov/typ. Funguje dobre pri práci so zjednodušenými alebo relačnými údajmi, ale nepodporuje nastavenie typov vnorených záznamov, tabuliek, zoznamov ani neumožňuje opätovné použitie definícií typu v tabuľkách alebo entitách.

V prípade TripPin obsahujú údaje v entitách Ľudia a airports štruktúrované stĺpce a dokonca zdieľajú typ (Location) na znázornenie informácií o adrese. Namiesto definovania párov Názov/Typ v tabuľke schém definujete každú z týchto entít pomocou vlastných deklarácií typu M.

Tu je stručný obnovovač typov v jazyku M podľa Špecifikácie jazyka:

Hodnota typu je hodnota, ktorá klasifikuje iné hodnoty. Znamená to, že hodnota, ktorá je klasifikovaná typom , zodpovedá danmu typu. Systém typov jazyka M sa skladá z nasledujúcich druhov typov:

  • Jednoduché typy, ktoré klasifikujú primitívne hodnoty (binary, , datedatetime, datetimezone, duration, list, logical, null, number, record, text, , time, type) a zahŕňajú aj množstvo abstraktných typov (function, table, anya none)
  • Typy záznamov, ktoré klasifikujú hodnoty záznamu na základe názvov polí a typov hodnôt
  • Typy zoznamu, ktoré klasifikujú zoznamy pomocou jedného základného typu položky
  • Typy funkcií, ktoré klasifikujú hodnoty funkcií na základe typov ich parametrov a vrátených hodnôt
  • Typy tabuľky, ktoré klasifikujú hodnoty tabuľky na základe názvov stĺpcov, typov stĺpcov a kľúčov
  • Typy s povolenou hodnotou Null, ktoré klasifikujú hodnotu null okrem všetkých hodnôt klasifikovaných základným typom
  • Typy typov, ktoré klasifikujú hodnoty, ktoré sú typmi

Pomocou nespracovaného výstupu JSON, ktorý získate (a/alebo hľadáte definície v $metadata služby), môžete definovať nasledujúce typy záznamov, ktoré predstavujú komplexné typy OData:

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
];

Všimnite si, ako LocationType odkazy na CityType a LocType predstavujú jeho štruktúrované stĺpce.

Pre entity najvyššej úrovne (ktoré chcete reprezentovať ako tabuľky) definujete typy tabuliek:

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
];

Potom aktualizujete SchemaTable premennú (ktorú používate ako "vyhľadávaciu tabuľku" pre entitu na mapovanie typov) a použijete tieto nové definície typu:

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

Vynucovanie schémy pomocou typov

Na vynútenie schémy na údajoch, podobne ako v predchádzajúcej lekcii, sa spoliehate SchemaTransformTable na bežnú funkciu (Table.ChangeType). Na rozdiel od SchemaTransformTable, Table.ChangeType berie skutočný typ tabuľky jazyka M ako argument a použije vašu schému rekurzívne pre všetky vnorené typy. Podpis vyzerá takto:

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

Úplný zoznam kódov pre Table.ChangeType funkciu nájdete v súbore Table.ChangeType.pqm .

Poznámka

Z dôvodu flexibility je možné funkciu použiť v tabuľkách, ako aj zoznamy záznamov (čo je spôsob, akým by boli tabuľky zastúpené v dokumente JSON).

Potom je potrebné aktualizovať kód konektora, aby ste zmenili schema parameter z table hodnoty na type, a pridať volanie do Table.ChangeType funkcie v 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 aktualizuje sa a použije zoznam polí zo schémy (s cieľom poznať názvy toho, čo sa má rozbaliť, keď získate výsledky), ale skutočné vynútenie schémy ponechá na hodnotu 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];

Potvrdzovanie nastavenia vnorených typov

Definícia pre vašu PeopleType teraz nastaví Emails pole na zoznam textu ({text}). Ak používate typy správne, volanie funkcie Type.ListItem v teste zariadenia by sa teraz malo vrátiť type text namiesto type any.

Pri opätovnom spustení testov zariadenia sa zobrazí, že teraz všetky prejdú.

Unit test with success.

Refaktorovanie bežného kódu do samostatných súborov

Poznámka

Nástroj M bude mať v budúcnosti lepšiu podporu odkazovania na externé moduly/spoločný kód, tento prístup by vás však mal dovtedy preniesť do tohto bodu.

V tomto momente má vaše rozšírenie takmer toľko "bežného" kódu ako kód konektora TripPin. V budúcnosti budú tieto spoločné funkcie buď súčasťou vstavanej knižnice štandardných funkcií , alebo ich budete môcť odkazovať z iného rozšírenia. Teraz refaktorujte kód takto:

  1. Premiestnite opätovne použiteľné funkcie do samostatných súborov (.pqm).
  2. Nastavte vlastnosť Build Action v súbore na compile, aby ste sa uistili, že sa zahrnie do súboru s príponou počas kompilovania.
  3. Definujte funkciu na načítanie kódu pomocou funkcie Expression.Evaluate.
  4. Načítajte každú z bežných funkcií, ktoré chcete použiť.

Kód, ktorý chcete vykonať, je súčasťou nasledujúceho úryvku:

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");

Záver

Tento kurz vykonal niekoľko vylepšení v spôsobe vynucovania schémy na údajoch, ktoré získate z rozhrania REST API. Konektor sa v súčasnosti pevne kóduje s informáciami o schéme, ktorá má vplyv na výkon v režime runtime, ale nedokáže sa prispôsobiť zmenám v nadčasoch metaúdajov služby. Budúce kurzy prejdú na čisto dynamický prístup, ktorý bude odvodzovať schému z $metadata dokumentu služby.

Okrem zmien schémy pridal tento kurz testy zariadenia pre váš kód a refaktoroval bežné pomocné funkcie do samostatných súborov, aby sa zlepšila celková čitateľnosť.

Ďalšie kroky

TripPin Part 8 – Pridanie diagnostiky