TripPin ส่วนที่ 7 - Schema ขั้นสูงที่มีชนิด M
หมายเหตุ
เนื้อหานี้อ้างอิงเนื้อหาจากการดําเนินการแบบดั้งเดิมสําหรับการทดสอบหน่วยใน Visual Studio เนื้อหาจะได้รับการอัปเดตในอนาคตอันใกล้เพื่อครอบคลุมเฟรมเวิร์กการทดสอบ Power Query SDK ใหม่
บทช่วยสอนแบบหลายส่วนนี้ครอบคลุมการสร้างส่วนขยายแหล่งข้อมูลใหม่สําหรับ Power Query บทช่วยสอนมีไว้ให้ทําตามลําดับ —บทเรียนแต่ละบทจะสร้างขึ้นบนตัวเชื่อมต่อที่สร้างขึ้นในบทเรียนที่แล้ว โดยการเพิ่มความสามารถใหม่ให้กับตัวเชื่อมต่อของคุณแบบเพิ่มหน่วย
ในบทเรียนนี้ คุณจะ:
- บังคับใช้ Schema ของตารางโดยใช้ ชนิด M
- ตั้งค่าชนิดสําหรับระเบียนและรายการที่ซ้อนกัน
- รหัส Refactor สําหรับการนํากลับมาใช้ใหม่และการทดสอบหน่วย
ในบทเรียนก่อนหน้านี้ คุณได้กําหนดสคีมาตารางของคุณโดยใช้ระบบ "ตาราง Schema" อย่างง่าย วิธีการตาราง Schema นี้ทํางานได้กับ REST API/เชื่อมต่อ ors หลายตัว แต่บริการที่ส่งกลับชุดข้อมูลที่สมบูรณ์หรือซ้อนกันลึกอาจได้รับประโยชน์จากวิธีการในบทช่วยสอนนี้ซึ่งใช้ประโยชน์จากระบบชนิด M
บทเรียนนี้จะแนะนําคุณผ่านขั้นตอนต่อไปนี้:
- การเพิ่มการทดสอบหน่วย
- การกําหนดชนิด M แบบกําหนดเอง
- การบังคับใช้ Schema โดยใช้ชนิด
- การปรับโครงสร้างโค้ดทั่วไปใหม่เป็นไฟล์แยกต่างหาก
การเพิ่มการทดสอบหน่วย
ก่อนที่คุณจะเริ่มใช้ตรรกะ Schema ขั้นสูง คุณจะต้องเพิ่มชุดการทดสอบหน่วยลงในตัวเชื่อมต่อของคุณเพื่อลดโอกาสที่จะมีการทําลายบางอย่างโดยไม่ได้ตั้งใจ การทดสอบหน่วยทํางานดังนี้:
- คัดลอกรหัสทั่วไปจากตัวอย่าง UnitTest ลงในไฟล์ของคุณ
TripPin.query.pq
- เพิ่มการประกาศส่วนที่ด้านบนของไฟล์ของคุณ
TripPin.query.pq
- สร้าง เรกคอร์ดที่แชร์ (เรียกว่า
TripPin.UnitTest
) Fact
กําหนด สําหรับแต่ละการทดสอบ- เรียกใช้
Facts.Summarize()
เพื่อเรียกใช้การทดสอบทั้งหมด - อ้างอิงการเรียกก่อนหน้าเป็นค่าที่ใช้ร่วมกันเพื่อให้แน่ใจว่าได้รับการประเมินเมื่อเรียกใช้โครงการใน 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];
การเลือกเรียกใช้โครงการจะประเมินข้อเท็จจริงทั้งหมดและให้ผลลัพธ์รายงานที่มีลักษณะดังนี้:
เมื่อใช้หลักการบางอย่างจาก การพัฒนาที่ขับเคลื่อนด้วยการทดสอบ ตอนนี้คุณจะต้องเพิ่มการทดสอบที่ปัจจุบันล้มเหลว แต่จะได้รับการเติมและแก้ไขในเร็ว ๆ นี้ (ในช่วงท้ายของบทช่วยสอนนี้) โดยเฉพาะ คุณจะเพิ่มการทดสอบที่ตรวจสอบหนึ่งในระเบียนที่ซ้อนกัน (อีเมล) ที่คุณได้รับกลับมาในเอนทิตีบุคคล
Fact("Emails is properly typed", type text, Type.ListItem(Value.Type(People{0}[Emails])))
หากคุณเรียกใช้รหัสอีกครั้งคุณควรเห็นว่าคุณมีการทดสอบที่ล้มเหลว
ในตอนนี้ คุณจําเป็นต้องใช้ฟังก์ชันการทํางานเพื่อทําให้งานนี้ใช้งานได้
การกําหนดชนิด M แบบกําหนดเอง
วิธีการบังคับใช้ Schema ใน บทเรียน ก่อนหน้านี้ใช้ "ตาราง schema" ซึ่งกําหนดเป็นคู่ชื่อ/ชนิด ทํางานได้ดีเมื่อทํางานกับข้อมูลแบบโครงสร้าง/ข้อมูลเชิงสัมพันธ์ แต่ไม่สนับสนุนการตั้งค่าชนิดบนระเบียน/ตาราง/รายการที่ซ้อนกัน หรือช่วยให้คุณสามารถนําข้อกําหนดประเภทไปใช้ใหม่ในตาราง/เอนทิตี้ได้
ในกรณี TripPin ข้อมูลในเอนทิตีบุคคลและสนามบินจะประกอบด้วยคอลัมน์ที่มีโครงสร้าง และแม้แต่แชร์ชนิด (Location
) สําหรับการแสดงข้อมูลที่อยู่ แทนที่จะกําหนดคู่ชื่อ/ชนิดในตาราง Schema คุณจะกําหนดแต่ละเอนทิตีเหล่านี้โดยใช้การประกาศชนิด M แบบกําหนดเอง
นี่คือการรีเฟรชด่วนเกี่ยวกับชนิดในภาษา M จากข้อมูลจําเพาะของภาษา:
ค่าชนิด คือค่าที่ใช้ในการจัดประเภทค่าอื่น ๆ ค่าที่จัดประเภทตามชนิดจะกล่าวได้ว่า สอดคล้องกับ ชนิดนั้น ระบบชนิด M ประกอบด้วยชนิดของชนิดต่อไปนี้:
- ชนิด Primitive ซึ่งจัดประเภทค่าดั้งเดิม (
binary
, ,date
,datetime
,datetimezone
,duration
,list
,null
logical
,number
,record
, ,text
,time
, )type
และยังรวมถึงจํานวนของชนิดนามธรรม (function
,table
,any
และnone
)- ชนิด 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
อ้างอิง CityType
และ LocType
เพื่อแสดงโครงสร้างคอลัมน์
สําหรับเอนทิตีระดับบนสุด (ที่คุณต้องการแสดงเป็นตาราง) คุณกําหนด ชนิดตาราง:
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
ใน บทเรียนก่อนหน้า
Table.ChangeType
ซึ่งแตกต่างจาก SchemaTransformTable
จะใช้ในชนิดตาราง M จริงเป็นอาร์กิวเมนต์ และจะใช้ Schema ของคุณซ้ําสําหรับชนิดที่ซ้อนกันทั้งหมด ลายเซ็นของมันมีลักษณะดังนี้:
Table.ChangeType = (table, tableType as type) as nullable table => ...
รายการรหัสแบบเต็มสําหรับTable.ChangeType
ฟังก์ชันสามารถพบได้ในไฟล์ Table.ChangeType.pqm
หมายเหตุ
เพื่อความยืดหยุ่น ฟังก์ชันสามารถใช้ได้ในตาราง ตลอดจนรายการระเบียน (ซึ่งเป็นวิธีการที่ตารางจะถูกแสดงในเอกสาร JSON)
จากนั้นคุณจําเป็นต้องอัปเดตรหัสตัวเชื่อมต่อเพื่อเปลี่ยนschema
พารามิเตอร์จาก เป็น table
type
และเพิ่มการเรียกไปยัง ในGetEntity
Table.ChangeType
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 any
เป็นtype text
เรียกใช้การทดสอบหน่วยของคุณอีกครั้งแสดงว่าขณะนี้กําลังผ่านทั้งหมด
การปรับโครงสร้างโค้ดทั่วไปใหม่เป็นไฟล์แยกต่างหาก
หมายเหตุ
กลไกจัดการ M จะได้รับการปรับปรุงการสนับสนุนสําหรับการอ้างอิงโมดูลภายนอก/รหัสทั่วไปในอนาคต แต่วิธีการนี้ควรดําเนินการแก่คุณจนถึงเวลานั้น
ในตอนนี้ ส่วนขยายของคุณเกือบมีรหัส "ทั่วไป" มากเท่ากับรหัสตัวเชื่อมต่อ TripPin ในอนาคต ฟังก์ชันทั่วไปเหล่านี้จะเป็นส่วนหนึ่งของไลบรารีฟังก์ชันมาตรฐานที่มีอยู่ภายใน หรือคุณจะสามารถอ้างอิงฟังก์ชันเหล่านั้นจากส่วนขยายอื่นได้ สําหรับตอนนี้ คุณปรับโครงสร้างโค้ดของคุณใหม่ด้วยวิธีต่อไปนี้:
- ย้ายฟังก์ชันที่นํามาใช้ใหม่ได้ไปยังไฟล์แยกต่างหาก (.pqm)
- ตั้งค่าคุณสมบัติการดําเนินการสร้างบนไฟล์เพื่อคอมไพล์เพื่อให้แน่ใจว่าไฟล์นั้นรวมอยู่ในไฟล์ส่วนขยายของคุณในระหว่างการสร้าง
- กําหนดฟังก์ชันเพื่อโหลดโค้ดโดยใช้ Expression.Evaluate
- โหลดฟังก์ชันทั่วไปแต่ละฟังก์ชันที่คุณต้องการใช้
โค้ดสําหรับทําเช่นนี้จะรวมอยู่ในส่วนย่อยด้านล่าง:
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 อย่างหนัก ซึ่งมีประโยชน์ประสิทธิภาพในขณะทํางาน แต่ไม่สามารถปรับให้เข้ากับการเปลี่ยนแปลงในเมตาดาต้าของบริการในช่วงต่อเวลา บทช่วยสอนในอนาคตจะย้ายไปยังวิธีการแบบไดนามิกอย่างแท้จริงซึ่งจะอนุมาน Schema จากเอกสาร$metadata ของบริการ
นอกเหนือจากการเปลี่ยนแปลง Schema แล้ว บทช่วยสอนนี้ยังเพิ่มการทดสอบหน่วยสําหรับโค้ดของคุณ และปรับโครงสร้างฟังก์ชันผู้ช่วยเหลือทั่วไปใหม่เป็นไฟล์แยกต่างหากเพื่อปรับปรุงความสามารถในการอ่านโดยรวม