Sdílet prostřednictvím


TripPin část 7 – Pokročilé schéma s typy M

Poznámka:

Tento obsah aktuálně odkazuje na obsah ze starší implementace pro testování jednotek v sadě Visual Studio. Obsah se bude v blízké budoucnosti aktualizovat tak, aby zahrnoval novou testovací architekturu sady Power Query SDK.

Tento vícedílný kurz popisuje vytvoření nového rozšíření zdroje dat pro Power Query. Tento kurz se má provést postupně – každá lekce vychází z konektoru vytvořeného v předchozích lekcích a postupně přidává do konektoru nové funkce.

V této lekci:

  • Vynucení schématu tabulky pomocí M Types
  • Nastavení typů pro vnořené záznamy a seznamy
  • Refaktoring kódu pro opakované použití a testování jednotek

V předchozí lekci jste definovali schémata tabulek pomocí jednoduchého systému Tabulka schémat. Tento přístup tabulky schématu funguje pro mnoho rozhraní REST API nebo datových Připojení orů, ale služby, které vracejí úplné nebo hluboko vnořené datové sady, můžou využít přístup v tomto kurzu, který využívá systém typů M.

Tato lekce vás provede následujícími kroky:

  1. Přidání testů jednotek
  2. Definování vlastních typů M
  3. Vynucení schématu pomocí typů
  4. Refaktoring společného kódu do samostatných souborů

Přidání testů jednotek

Než začnete používat pokročilou logiku schématu, přidáte do konektoru sadu testů jednotek, abyste snížili pravděpodobnost neúmyslného narušení něčeho. Testování částí funguje takto:

  1. Zkopírujte běžný kód z ukázky UnitTest do souboruTripPin.query.pq.
  2. Přidejte deklaraci oddílu do horní části TripPin.query.pq souboru.
  3. Vytvoření sdíleného záznamu (volanýTripPin.UnitTest).
  4. Definujte pro Fact každý test.
  5. Volání Facts.Summarize() pro spuštění všech testů
  6. Použijte odkaz na předchozí volání jako sdílenou hodnotu, abyste zajistili, že se vyhodnotí při spuštění projektu v sadě 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];

Výběr spuštění v projektu vyhodnotí všechna fakta a poskytne vám výstup sestavy, který vypadá takto:

Počáteční test jednotek.

Pomocí některých principů z vývoje řízeného testy teď přidáte test, který momentálně selže, ale brzy se znovu vytvoří a opraví (na konci tohoto kurzu). Konkrétně přidáte test, který zkontroluje jeden z vnořených záznamů (e-mailů), který vrátíte zpět do entity Lidé.

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

Pokud kód spustíte znovu, měli byste vidět, že máte neúspěšný test.

Test jednotek s chybou

Teď stačí implementovat funkce, aby to fungovalo.

Definování vlastních typů M

Přístup k vynucení schématu v předchozí lekci použil tabulky schématu definované jako páry Name/Type. Funguje dobře při práci s plochými nebo relačními daty, ale nepodporuje nastavení typů u vnořených záznamů, tabulek a seznamů nebo umožňuje opakovaně používat definice typů napříč tabulkami nebo entitami.

V případě TripPin data v entitách Lidé a Letiště obsahují strukturované sloupce a dokonce sdílejí typ (Location) pro reprezentaci informací o adrese. Místo definování dvojic Name/Type v tabulce schématu definujete každou z těchto entit pomocí vlastních deklarací typu M.

Tady je rychlý aktualizační nástroj o typech v jazyce M ze specifikace jazyka:

Hodnota typu je hodnota, která klasifikuje jiné hodnoty. Hodnota klasifikovaná typem je označena tak, aby odpovídala danému typu. Systém typů M se skládá z následujících typů typů:

  • Primitivní typy, které klasifikují primitivní hodnoty (binary, date, , datetimezonedatetime, durationlist, logical, , numbernull, record, text, typetime) a zahrnují také řadu abstraktních typů (function, table, anya none)
  • Typy záznamů, které klasifikují hodnoty záznamů na základě názvů polí a typů hodnot
  • Typy seznamů, které klasifikují seznamy pomocí jednoho základního typu položky
  • Typy funkcí, které klasifikují hodnoty funkcí na základě typů jejich parametrů a návratových hodnot
  • Typy tabulek, které klasifikují hodnoty tabulky na základě názvů sloupců, typů sloupců a klíčů
  • Typy s možnou hodnotou null, které kromě všech hodnot klasifikovaných základním typem klasifikují hodnotu null.
  • Typy typů, které klasifikují hodnoty, které jsou typy

Pomocí nezpracovaného výstupu JSON, který získáte (nebo hledáte definice v $metadata služby), můžete definovat následující typy záznamů, které představují 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šimněte si, jak LocationType odkazy a CityTypeLocType představují jeho strukturované sloupce.

Pro entity nejvyšší úrovně (které chcete reprezentovat jako tabulky), definujete typy tabulek:

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 proměnnou (kterou použijete jako vyhledávací tabulku pro mapování typů entit) tak, aby používala tyto nové definice typu:

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

Vynucení schématu pomocí typů

Při vynucování schématu u dat budete spoléhat na společnou funkci (Table.ChangeType), podobně jako jste použili SchemaTransformTable v předchozí lekci. Na rozdíl od SchemaTransformTabletypu Table.ChangeType skutečné tabulky M jako argumentu použije schéma rekurzivně pro všechny vnořené typy. Jeho podpis vypadá takto:

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

Úplný výpis kódu pro Table.ChangeType funkci najdete v souboru Table.ChangeType.pqm .

Poznámka:

Pro flexibilitu lze funkci použít u tabulek a také seznamy záznamů (což je způsob znázornění tabulek v dokumentu JSON).

Pak potřebujete aktualizovat kód konektoru, aby se změnil schema parametr z a table na , typea přidat volání do 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 je aktualizována tak, aby používala seznam polí ze schématu (abyste věděli názvy toho, co se má rozbalit při získání výsledků), ale ponechá skutečné vynucení schématu GetEntityna .

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

Potvrzení nastavení vnořených typů

Definice teď PeopleType nastaví Emails pole na seznam textu ({text}). Pokud používáte typy správně, volání Type.ListItem v testu jednotek by se teď mělo vracet type text spíše než type any.

Opětovné spuštění testů jednotek ukazuje, že teď všechny prošly.

Test jednotek s úspěchem.

Refaktoring společného kódu do samostatných souborů

Poznámka:

Modul M bude mít v budoucnu vylepšenou podporu pro odkazování na externí moduly nebo společný kód, ale tento přístup by vás měl projít až do té doby.

V tomto okamžiku má vaše rozšíření téměř tolik "společného" kódu jako kód konektoru TripPin. V budoucnu budou tyto běžné funkce buď součástí integrované standardní knihovny funkcí, nebo na ně budete moct odkazovat z jiného rozšíření. Prozatím refaktorujete kód následujícím způsobem:

  1. Přesuňte opakovaně použitelné funkce do samostatných souborů (.pqm).
  2. Nastavte vlastnost Akce sestavení souboru na Zkompilovat, abyste se ujistili, že je součástí souboru s příponou během sestavení.
  3. Definujte funkci pro načtení kódu pomocí expression.Evaluate.
  4. Načtěte všechny běžné funkce, které chcete použít.

Kód, který to provede, je součástí následujícího fragmentu kódu:

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ávěr

V tomto kurzu jsme provedli řadu vylepšení způsobu vynucování schématu u dat, která získáte z rozhraní REST API. Konektor v současné době pevně zakóduje informace o schématu, které mají výhodu výkonu za běhu, ale nemůže se přizpůsobit změnám v metadatech služby přesčas. Budoucí kurzy se přesunou na čistě dynamický přístup, který odvodí schéma z $metadata dokumentu služby.

Kromě změn schématu tento kurz přidal testy jednotek pro váš kód a refaktoroval běžné pomocné funkce do samostatných souborů, aby se zlepšila celková čitelnost.

Další kroky

TripPin – část 8 – přidání diagnostiky