แชร์ผ่าน


TripPin ส่วนที่ 7 - สคีมาขั้นสูงที่มีประเภท M

Note

ปัจจุบันเนื้อหานี้อ้างอิงเนื้อหาจากการใช้งานแบบเดิมสําหรับการทดสอบหน่วยใน Visual Studio เนื้อหาจะได้รับการอัปเดตในอนาคตเพื่อให้ครอบคลุม เฟรมเวิร์กการทดสอบ Power Query SDK ใหม่

บทช่วยสอนแบบหลายส่วนนี้ครอบคลุมการสร้างส่วนขยายแหล่งข้อมูลใหม่สําหรับ Power Query บทช่วยสอนมีไว้ให้ทําตามลําดับ—แต่ละบทเรียนจะสร้างขึ้นจากตัวเชื่อมต่อที่สร้างขึ้นในบทเรียนก่อนหน้า โดยจะเพิ่มความสามารถใหม่ให้กับตัวเชื่อมต่อของคุณทีละน้อย

ในบทเรียนนี้ คุณ:

  • บังคับใช้ Schema ตารางโดยใช้ M Types
  • ตั้งค่าชนิดสําหรับระเบียนและรายการที่ซ้อนกัน
  • รหัสรีแฟคเตอร์สําหรับการนํากลับมาใช้ใหม่และการทดสอบหน่วย

ในบทเรียนก่อนหน้านี้ คุณได้กําหนดสคีมาตารางของคุณโดยใช้ระบบ "ตารางสคีมา" อย่างง่าย วิธีการตารางสคีมานี้ใช้ได้กับ REST API/ตัวเชื่อมต่อข้อมูลจํานวนมาก แต่บริการที่ส่งคืนชุดข้อมูลที่สมบูรณ์หรือซ้อนกันอย่างลึกซึ้งอาจได้รับประโยชน์จากวิธีการในบทช่วยสอนนี้ ซึ่งใช้ระบบชนิด M

บทเรียนนี้จะแนะนําคุณตลอดขั้นตอนต่อไปนี้:

  1. การเพิ่มการทดสอบหน่วย
  2. การกําหนดประเภท M แบบกําหนดเอง
  3. การบังคับใช้ Schema โดยใช้ชนิด
  4. การปรับโครงสร้างโค้ดทั่วไปเป็นไฟล์แยกต่างหาก

การเพิ่มการทดสอบหน่วย

ก่อนที่คุณจะเริ่มใช้ตรรกะสคีมาขั้นสูง คุณต้องเพิ่มชุดการทดสอบหน่วยให้กับตัวเชื่อมต่อของคุณเพื่อลดโอกาสที่จะทําลายบางสิ่งโดยไม่ได้ตั้งใจ การทดสอบหน่วยทํางานดังนี้:

  1. คัดลอกรหัสทั่วไปจากตัวอย่าง UnitTest ลงในไฟล์ของคุณTripPin.query.pq
  2. เพิ่มการประกาศส่วนที่ด้านบนของ TripPin.query.pq ไฟล์
  3. สร้างเรกคอร์ด ที่ใช้ร่วมกัน (เรียกว่า TripPin.UnitTest)
  4. กําหนด a Fact สําหรับการทดสอบแต่ละครั้ง
  5. โทร Facts.Summarize() เพื่อเรียกใช้การทดสอบทั้งหมด
  6. อ้างอิงการเรียกก่อนหน้านี้เป็นค่าที่ใช้ร่วมกันเพื่อให้แน่ใจว่าได้รับการประเมินเมื่อโครงการถูกเรียกใช้ใน 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];

การเลือกเรียกใช้ในโครงการจะประเมินข้อเท็จจริงทั้งหมด และให้ผลลัพธ์รายงานที่มีลักษณะดังนี้:

สกรีนช็อตของแท็บ ผลลัพธ์ ของผลลัพธ์คิวรี M ที่แสดงผลลัพธ์รายงานที่สําเร็จ

การใช้หลักการบางอย่างจาก การพัฒนาที่ขับเคลื่อนด้วยการทดสอบตอนนี้คุณเพิ่มการทดสอบที่ล้มเหลวในปัจจุบัน แต่สามารถนําไปใช้ใหม่และแก้ไขได้ในไม่ช้า (ในตอนท้ายของบทช่วยสอนนี้) โดยเฉพาะอย่างยิ่ง คุณเพิ่มการทดสอบที่ตรวจสอบหนึ่งในเรกคอร์ดที่ซ้อนกัน (อีเมล) ที่คุณได้รับกลับมาในเอนทิตี บุคคล

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

หากคุณเรียกใช้โค้ดอีกครั้ง คุณจะเห็นว่าคุณมีการทดสอบที่ล้มเหลว

สกรีนช็อตของแท็บผลลัพธ์ของผลลัพธ์คิวรี M ที่แสดงการทดสอบที่ล้มเหลว

ตอนนี้คุณเพียงแค่ต้องใช้ฟังก์ชันการทํางานเพื่อให้ใช้งานได้

การกําหนดประเภท M แบบกําหนดเอง

วิธีการบังคับใช้ Schema ใน บทเรียนก่อนหน้านี้ ใช้ "ตาราง Schema" ที่กําหนดเป็นคู่ชื่อ/ชนิด ทํางานได้ดีเมื่อทํางานกับข้อมูลแบบแบน/เชิงสัมพันธ์ แต่ไม่สนับสนุนการตั้งค่าชนิดในเรกคอร์ด/ตาราง/รายการที่ซ้อนกัน หรืออนุญาตให้คุณนําคําจํากัดความชนิดกลับมาใช้ใหม่ในตาราง/เอนทิตี

ในกรณี TripPin ข้อมูลในเอนทิตี บุคคลและสนามบิน ประกอบด้วยคอลัมน์ที่มีโครงสร้าง และแม้กระทั่งใช้ชนิด (Location) ร่วมกันสําหรับแสดงข้อมูลที่อยู่ แทนที่จะกําหนดคู่ชื่อ/ชนิดในตาราง Schema คุณต้องกําหนดเอนทิตีเหล่านี้แต่ละรายการโดยใช้การประกาศชนิด M แบบกําหนดเอง

ต่อไปนี้เป็นการทบทวนอย่างรวดเร็วเกี่ยวกับชนิดในภาษา M จากข้อมูลจําเพาะของภาษา

ค่าชนิด คือค่าที่ใช้ในการจัดประเภทค่าอื่น ๆ ค่าที่จัดประเภทตามชนิดจะกล่าวได้ว่า สอดคล้องกับ ชนิดนั้น ระบบชนิด M ประกอบด้วยชนิดของชนิดต่อไปนี้:

  • ประเภทดั้งเดิมซึ่งจําแนกค่าดั้งเดิม (binary, , , datetime, numberdurationdatetimezonelistrecordtypelogicaltimetextnull) และยังรวมถึงประเภทนามธรรมจํานวนหนึ่ง (function, table, any, และ )nonedate
  • ชนิด Record ซึ่งจัดประเภทค่าเรกคอร์ดโดยยึดตามชื่อเขตข้อมูลและชนิดค่า
  • ชนิด List ซึ่งจัดประเภทรายการโดยใช้ชนิดฐานของหน่วยข้อมูลเดียว
  • ชนิดฟังก์ชัน ซึ่งจัดประเภทค่าฟังก์ชันที่ยึดตามชนิดของพารามิเตอร์และค่าผลลัพธ์
  • ชนิด Table ซึ่งจัดประเภทค่าตารางโดยยึดตามชื่อคอลัมน์ ชนิดคอลัมน์ และคีย์
  • ชนิด Nullable ซึ่งจัดประเภทค่า null นอกเหนือจากค่าทั้งหมดที่จําแนกตามชนิดฐาน
  • ชนิด Type ซึ่งจัดประเภทค่าที่เป็นชนิด

การใช้เอาต์พุต JSON ดิบที่คุณได้รับ (และ/หรือค้นหาคําจํากัดความใน $metadata ของบริการ) คุณสามารถกําหนดชนิดเรกคอร์ดต่อไปนี้เพื่อแสดงชนิดที่ซับซ้อนของ 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
];

สังเกตว่าการ LocationType อ้างอิง และ CityTypeLocType เพื่อแสดงคอลัมน์ที่มีโครงสร้างอย่างไร

สําหรับเอนทิตีระดับบนสุด (ที่คุณต้องการแสดงเป็นตาราง) คุณกําหนด ชนิดตาราง:

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

จากนั้น คุณอัปเดตตัวแปรของคุณ SchemaTable (ซึ่งคุณใช้เป็น "ตารางการค้นหา" สําหรับเอนทิตีเพื่อพิมพ์การแม็ป) เพื่อใช้ข้อกําหนดชนิดใหม่เหล่านี้:

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

การบังคับใช้ Schema โดยใช้ประเภท

คุณใช้ฟังก์ชันทั่วไป (Table.ChangeType) เพื่อบังคับใช้ Schema บนข้อมูลของคุณ เหมือนกับที่คุณใช้ในSchemaTransformTableบทเรียนก่อนหน้า ซึ่งแตกต่างจาก SchemaTransformTableใช้ Table.ChangeType ประเภทตาราง M จริงเป็นอาร์กิวเมนต์ และใช้สคีมาของคุณ แบบเรียกซ้ํา สําหรับชนิดที่ซ้อนกันทั้งหมด ลายเซ็นมีลักษณะดังนี้:

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

รายการรหัสแบบเต็มสําหรับTable.ChangeTypeฟังก์ชันสามารถพบได้ในไฟล์ Table.ChangeType.pqm

Note

เพื่อความยืดหยุ่น ฟังก์ชันนี้สามารถใช้กับตาราง เช่นเดียวกับรายการของเรกคอร์ด (ซึ่งเป็นวิธีที่ตารางจะแสดงในเอกสาร JSON)

จากนั้นคุณต้องอัปเดตรหัสตัวเชื่อมต่อเพื่อเปลี่ยนschemaพารามิเตอร์จาก a table เป็น a typeและเพิ่มการเรียกเข้า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ได้รับการอัปเดตเพื่อใช้รายการของฟิลด์จาก Schema (เพื่อทราบชื่อของสิ่งที่จะขยายเมื่อคุณได้รับผลลัพธ์) แต่ปล่อยให้การบังคับใช้ Schema จริงเป็น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];

การยืนยันว่ามีการตั้งค่าประเภทที่ซ้อนกัน

คําจํากัดความสําหรับตอนนี้ของคุณจะ PeopleType ตั้งค่า Emails เขตข้อมูลเป็นรายการข้อความ ({text}) ถ้าคุณใช้ชนิดอย่างถูกต้อง การเรียก Type.ListItem ในการทดสอบหน่วยของคุณควรส่งคืนtype textแทนที่จะเป็นtype any

การเรียกใช้การทดสอบหน่วยของคุณอีกครั้งแสดงให้เห็นว่าตอนนี้พวกเขาผ่านทั้งหมด

สกรีนช็อตของแท็บ ผลลัพธ์ ของผลลัพธ์คิวรี M ที่แสดงการทดสอบหน่วยที่ประสบความสําเร็จ

การปรับโครงสร้างโค้ดทั่วไปเป็นไฟล์แยกต่างหาก

Note

เอ็นจิ้น M จะได้รับการปรับปรุงการสนับสนุนสําหรับการอ้างอิงโมดูลภายนอก/รหัสทั่วไปในอนาคต แต่วิธีการนี้ควรจะดําเนินการจนกว่าจะถึงเวลานั้น

ณ จุดนี้ ส่วนขยายของคุณเกือบจะมีรหัส "ทั่วไป" มากพอๆ กับรหัสตัวเชื่อมต่อ TripPin ในอนาคตฟังก์ชัน ทั่วไป เหล่านี้จะเป็นส่วนหนึ่งของไลบรารีฟังก์ชันมาตรฐานในตัวหรือคุณจะสามารถอ้างอิงจากส่วนขยายอื่นได้ สําหรับตอนนี้ คุณปรับโครงสร้างโค้ดของคุณด้วยวิธีต่อไปนี้:

  1. ย้ายฟังก์ชันที่นํากลับมาใช้ใหม่ได้ไปยังไฟล์แยกต่างหาก (.pqm)
  2. ตั้งค่าคุณสมบัติ Build Action บนไฟล์เป็น Compile เพื่อให้แน่ใจว่าจะรวมอยู่ในไฟล์ส่วนขยายของคุณในระหว่างการสร้าง
  3. กําหนดฟังก์ชันเพื่อโหลดโค้ดโดยใช้ Expression.Evaluate
  4. โหลดฟังก์ชันทั่วไปแต่ละฟังก์ชันที่คุณต้องการใช้

โค้ดเพื่อทําการปรับโครงสร้างใหม่นี้จะรวมอยู่ในส่วนย่อยต่อไปนี้:

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

บทสรุป

บทช่วยสอนนี้ได้ทําการปรับปรุงวิธีการบังคับใช้ Schema กับข้อมูลที่คุณได้รับจาก REST API หลายประการ ขณะนี้ตัวเชื่อมต่อกําลังฮาร์ดโค้ดข้อมูล Schema ซึ่งมีประโยชน์ด้านประสิทธิภาพในขณะรันไทม์ แต่ไม่สามารถปรับให้เข้ากับการเปลี่ยนแปลงในข้อมูลเมตาของบริการล่วงเวลาได้ บทช่วยสอนในอนาคตจะย้ายไปใช้วิธีการแบบไดนามิกล้วนๆ ซึ่งจะอนุมานสคีมาจากเอกสาร $metadata ของบริการ

นอกเหนือจากการเปลี่ยนแปลงสคีมาแล้วบทช่วยสอนนี้ยังเพิ่มการทดสอบหน่วยสําหรับโค้ดของคุณและปรับโครงสร้างฟังก์ชันตัวช่วยทั่วไปเป็นไฟล์แยกต่างหากเพื่อปรับปรุงความสามารถในการอ่านโดยรวม

ขั้นตอนถัดไป

TripPin ตอนที่ 8 - การเพิ่มการวินิจฉัย