Megosztás a következőn keresztül:


TripPin 7. rész – Speciális séma M típusok használatával

Feljegyzés

Ez a tartalom jelenleg egy örökölt implementációból származó tartalmakra hivatkozik a Visual Studióban végzett egységteszteléshez. A tartalom a közeljövőben frissülni fog, hogy lefedje az új Power Query SDK tesztelési keretrendszert.

Ez a többrészes oktatóanyag a Power Query új adatforrásbővítményének létrehozását ismerteti. Az oktatóanyagot egymás után kell elvégezni – minden lecke az előző leckékben létrehozott összekötőre épül, és növekményesen új képességeket ad hozzá az összekötőhöz.

Ebben a leckében a következőt fogja:

  • Táblaséma kényszerítése M-típusok használatával
  • Beágyazott rekordok és listák típusainak beállítása
  • Újrabontási kód újrafelhasználáshoz és egységteszteléshez

Az előző leckében egy egyszerű "Sématábla" rendszer használatával definiálta a táblázatsémákat. Ez a sématábla-megközelítés számos REST API-hoz/Adat-Csatlakozás orhoz használható, de a teljes vagy mélyen beágyazott adathalmazokat visszaadó szolgáltatások kihasználhatják az oktatóanyagban ismertetett megközelítést, amely az M típusú rendszert használja.

Ez a lecke végigvezeti a következő lépéseken:

  1. Egységtesztek hozzáadása.
  2. Egyéni M-típusok definiálása.
  3. Séma kényszerítése típusok használatával.
  4. A közös kód újrabontása külön fájlokba.

Egységtesztek hozzáadása

Mielőtt elkezdené használni a speciális sémalogikát, egységteszteket fog hozzáadni az összekötőhöz, hogy csökkentse annak esélyét, hogy véletlenül feltörjön valamit. Az egységtesztelés a következőképpen működik:

  1. Másolja a közös kódot a UnitTest-mintából a TripPin.query.pq fájlba.
  2. Adjon hozzá egy szakaszdeklarációt a TripPin.query.pq fájl tetejére.
  3. Megosztott rekord létrehozása (más néven TripPin.UnitTest).
  4. Adjon meg egy-egy Fact tesztet.
  5. Hívás Facts.Summarize() az összes teszt futtatásához.
  6. Hivatkozzon az előző hívásra megosztott értékként, hogy a rendszer kiértékelje a projektet a Visual Studióban való futtatásakor.
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];

Ha kiválasztja a futtatást a projekten, kiértékeli az összes tényt, és az alábbihoz hasonló jelentéskimenetet ad:

Kezdeti egységteszt.

A tesztalapú fejlesztés néhány alapelvét használva mostantól hozzáad egy olyan tesztet, amely jelenleg sikertelen, de hamarosan újra elérhetővé válik és javítható lesz (az oktatóanyag végére). Pontosabban egy olyan tesztet fog hozzáadni, amely ellenőrzi a Kapcsolatok entitásba visszahelyezett beágyazott rekordok (e-mailek) egyikét.

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

Ha újra futtatja a kódot, látnia kell, hogy sikertelen tesztje van.

Egységteszt meghibásodással.

Most már csak implementálnia kell a funkciót, hogy működjön.

Egyéni M-típusok definiálása

Az előző leckében a sémakényszerítési módszer név/típus párként definiált "sématáblákat" használt. Jól működik, ha egybesimított/relációs adatokkal dolgozik, de nem támogatja a beágyazott rekordok/táblák/listák típusok beállítását, vagy lehetővé teszi a típusdefiníciók újbóli felhasználását a táblákban/entitásokban.

A TripPin-esetben a Kapcsolatok és a Repülőtér entitások adatai strukturált oszlopokat tartalmaznak, és még a címadatok megadására szolgáló típust (Location) is megosztanak. A név-típus párok sématáblában való definiálása helyett ezeket az entitásokat egyéni M típusú deklarációkkal definiálja.

Íme egy gyors frissítés az M nyelv típusairól a Nyelvi specifikációból:

A típusérték olyan érték, amely más értékeket sorol be . A típus szerint besorolt értéknek az adott típusnak kell megfelelnie . Az M típusú rendszer a következő típusokból áll:

  • Primitív típusok, amelyek primitív értékeket osztályoznak (binary, date, datetime, datetimezone, durationlist, logicalnumberrecordnull, , text, ), time), typeés számos absztrakt típust is tartalmaznak (function, table, , anyés )none
  • Rekordtípusok, amelyek mezőnevek és értéktípusok alapján osztályozzák a rekordértékeket
  • Listatípusok, amelyek egyetlen elem alaptípusával osztályozzák a listákat
  • Függvénytípusok, amelyek a paraméterek típusai alapján osztályozzák a függvényértékeket, és visszaadják az értékeket
  • Táblázattípusok, amelyek oszlopnevek, oszloptípusok és kulcsok alapján sorolják be a táblaértékeket
  • Null értékű típusok, amelyek a null értéket az alaptípus szerint besorolt összes érték mellett osztályozzák
  • Típustípusok, amelyek a típusokat osztályozzák

A nyers JSON-kimenet használatával (és/vagy a szolgáltatás $metadata definícióinak keresésekor) az OData-összetett típusok megjelenítéséhez a következő rekordtípusokat határozhatja meg:

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

Figyelje meg, hogy a LocationType hivatkozások hogyan hivatkoznak a CityType strukturált oszlopokra, és LocType hogyan jelölik azokat.

A legfelső szintű entitásokhoz (amelyeket táblákként szeretne ábrázolni) a következő táblázattípusokat kell megadnia:

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

Ezután frissítenie kell a változót SchemaTable (amelyet entitások "keresési táblájaként" használ a leképezések beírásához) az alábbi új típusdefiníciók használatára:

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

Séma kényszerítése típusok használatával

A sémák az előző leckében használthoz SchemaTransformTable hasonlóan egy közös függvényre (Table.ChangeType) támaszkodnak. Ellentétben SchemaTransformTablea Table.ChangeType tényleges M táblatípussal argumentumként, és rekurzív módon alkalmazza a sémát az összes beágyazott típusra. Aláírása így néz ki:

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

A függvény teljes kódlistái Table.ChangeType a Table.ChangeType.pqm fájlban találhatók.

Feljegyzés

A rugalmasság érdekében a függvény táblákon, valamint rekordok listáján is használható (így jelennének meg a táblák egy JSON-dokumentumban).

Ezután frissítenie kell az összekötő kódját, hogy a schema paramétert egyről egyre table typemódosítsa, és fel kell vennie egy hívást Table.ChangeType a fájlba 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 a rendszer a séma mezőlistájának használatára frissül (az eredmények lekérésekor kiterjesztendő mezők neveinek megismeréséhez), de a tényleges sémakényszerítést a értékre GetEntityhagyja.

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

Annak ellenőrzése, hogy a beágyazott típusok be vannak-e állítva

A definíció PeopleType most egy szöveglistára ({text}) állítja be a Emails mezőt. Ha helyesen alkalmazza a típusokat, az egységteszt Type.ListItem hívásának most ahelyett kell visszatérnietype anytype text.

Az egységtesztek ismételt futtatása azt mutatja, hogy most már mind átmennek.

Egységteszt sikerrel.

Közös kód újrabontása külön fájlokba

Feljegyzés

Az M motor a jövőben nagyobb támogatást fog nyújtani a külső modulokra/közös kódra való hivatkozáshoz, de ezt a megközelítést addig is végig kell vinnie.

Ezen a ponton a bővítmény majdnem annyi "közös" kóddal rendelkezik, mint a TripPin-összekötő kódja. A jövőben ezek a gyakori függvények vagy a beépített standard függvénytár részei lesznek, vagy egy másik bővítményből hivatkozhat rájuk. Egyelőre a következő módon kell újrabontást eszközleni a kódon:

  1. Helyezze át az újrafelhasználható függvényeket a fájlok (.pqm) elkülönítéséhez.
  2. Állítsa a fájl Build Action tulajdonságát Fordításra, hogy biztosan szerepeljen a bővítményfájlban a buildelés során.
  3. Adjon meg egy függvényt a kód Expression.Evaluate használatával való betöltéséhez.
  4. Töltse be a használni kívánt gyakori függvényeket.

Az ehhez szükséges kódot az alábbi kódrészlet tartalmazza:

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

Összegzés

Ez az oktatóanyag számos fejlesztést végzett a REST API-ból kapott adatok séma-kényszerítésének módján. Az összekötő jelenleg nehezen kódozza a sémainformációit, amely futásidőben teljesítménnyel jár, de nem tud alkalmazkodni a szolgáltatás metaadatainak túlórájával kapcsolatos változásokhoz. A jövőbeli oktatóanyagok tisztán dinamikus megközelítésre kerülnek, amely a szolgáltatás $metadata dokumentumából fogja következtetni a sémát.

A sémamódosítások mellett ez az oktatóanyag egységteszteket is hozzáadott a kódhoz, és a közös segédfüggvényeket külön fájlokba bontotta az általános olvashatóság javítása érdekében.

Következő lépések

TripPin 8. rész – Diagnosztika hozzáadása