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:
- Přidání testů jednotek
- Definování vlastních typů M
- Vynucení schématu pomocí typů
- 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:
- Zkopírujte běžný kód z ukázky UnitTest do souboru
TripPin.query.pq
. - Přidejte deklaraci oddílu do horní části
TripPin.query.pq
souboru. - Vytvoření sdíleného záznamu (volaný
TripPin.UnitTest
). - Definujte pro
Fact
každý test. - Volání
Facts.Summarize()
pro spuštění všech testů - 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:
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.
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
, ,datetimezone
datetime
,duration
list
,logical
, ,number
null
,record
,text
,type
time
) a zahrnují také řadu abstraktních typů (function
,table
,any
anone
)- 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 CityType
LocType
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 SchemaTransformTable
typu 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 , type
a přidat volání do 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
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 GetEntity
na .
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.
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:
- Přesuňte opakovaně použitelné funkce do samostatných souborů (.pqm).
- Nastavte vlastnost Akce sestavení souboru na Zkompilovat, abyste se ujistili, že je součástí souboru s příponou během sestavení.
- Definujte funkci pro načtení kódu pomocí expression.Evaluate.
- 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.