다음을 통해 공유


TripPin 7부 - M 형식의 고급 스키마

참고 항목

이 콘텐츠는 현재 Visual Studio에서 단위 테스트를 위해 레거시 구현의 콘텐츠를 참조합니다. 콘텐츠는 새 파워 쿼리 SDK 테스트 프레임워크를 포함하도록 가까운 장래에 업데이트될 예정입니다.

이 다중 파트 자습서에서는 파워 쿼리에 대한 새 데이터 원본 확장의 생성에 대해 설명합니다. 이 자습서는 순차적으로 수행됩니다. 각 단원은 이전 단원에서 만든 커넥터를 기반으로 하여 커넥터에 새 기능을 증분 방식으로 추가합니다.

이 단원에서는 다음을 수행합니다.

  • M 형식을 사용하여 테이블 스키마 적용
  • 중첩된 레코드 및 목록에 대한 형식 설정
  • 재사용 및 단위 테스트를 위한 코드 리팩터링

이전 단원에서는 간단한 "스키마 테이블" 시스템을 사용하여 테이블 스키마를 정의했습니다. 이 스키마 테이블 접근 방식은 많은 REST API/데이터 커넥트에서 작동하지만 완전하거나 깊이 중첩된 데이터 집합을 반환하는 서비스는 M 형식 시스템을 활용하는 이 자습서의 접근 방식을 활용할 수 있습니다.

이 단원에서는 다음 단계를 안내합니다.

  1. 단위 테스트 추가
  2. 사용자 지정 M 형식 정의
  3. 형식을 사용하여 스키마 적용
  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];

프로젝트에서 실행을 선택하면 모든 팩트를 평가하고 다음과 같은 보고서 출력을 제공합니다.

초기 단위 테스트.

이제 테스트 기반 개발몇 가지 원칙을 사용하여 현재 실패하는 테스트를 추가하지만 곧 다시 구현되고 수정될 예정입니다(이 자습서가 끝날 때까지). 특히 사람 엔터티에서 다시 가져오는 중첩된 레코드(이메일) 중 하나를 검사 테스트를 추가합니다.

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

코드를 다시 실행하면 이제 실패한 테스트가 있음을 확인할 수 있습니다.

오류가 있는 단위 테스트입니다.

이제 이 기능을 구현하기만 하면 됩니다.

사용자 지정 M 형식 정의

이전 단원스키마 적용 방법은 이름/형식 쌍으로 정의된 "스키마 테이블"을 사용했습니다. 평면/관계형 데이터로 작업할 때는 잘 작동하지만 중첩된 레코드/테이블/목록에서 형식 설정을 지원하지 않거나 테이블/엔터티에서 형식 정의를 다시 사용할 수 없습니다.

TripPin의 경우 사람 및 공항 엔터티의 데이터에는 구조화된 열이 포함되며 주소 정보를 나타내는 형식(Location)도 공유합니다. 스키마 테이블에서 이름/형식 쌍을 정의하는 대신 사용자 지정 M 형식 선언을 사용하여 이러한 각 엔터티를 정의합니다.

다음은 언어 사양에서 M 언어의 형식에 대한 빠른 새로 고침입니다.

형식 값은 다른 값을 분류하는 값입니다. 형식으로 분류되는 값은 해당 형식을 준수한다고 합니다. M 형식 시스템은 다음과 같은 종류의 형식으로 구성됩니다.

  • 기본값(binary, ,, textdatedatetimezonedurationtypelisttimenulldatetimerecordnumberlogical) 을 분류하고 여러 추상 형식(function, table, anynone)도 포함하는 기본 형식
  • 필드 이름 및 값 형식에 따라 레코드 값을 분류하는 레코드 형식
  • 단일 항목 기본 형식을 사용하여 목록을 분류하는 목록 형식
  • 매개 변수 형식 및 반환 값에 따라 함수 값을 분류하는 함수 형식
  • 열 이름, 열 형식 및 키를 기반으로 테이블 값을 분류하는 테이블 형식
  • 기본 형식으로 분류된 모든 값 외에 null 값을 분류하는 Null 허용 형식
  • 형식인 값을 분류하는 형식 형식

가져오는 원시 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}    
});

형식을 사용하여 스키마 적용

이전 단원에서 사용한 SchemaTransformTable 것처럼 공통 함수(Table.ChangeType)를 사용하여 데이터에 스키마를 적용합니다. 달리 SchemaTransformTable, Table.ChangeType 실제 M 테이블 형식을 인수로 사용하고 모든 중첩된 형식에 대해 스키마 를 재귀적으로 적용합니다. 서명은 다음과 같습니다.

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

함수의 Table.ChangeType 전체 코드 목록은 Table.ChangeType.pqm 파일에서 찾을 수 있습니다.

참고 항목

유연성을 위해 테이블과 레코드 목록(JSON 문서에서 테이블이 표현되는 방식)에서 함수를 사용할 수 있습니다.

그런 다음 커넥터 코드를 업데이트하여 매개 변수를 schema a에서 a table type로 변경하고 호출을 추가해야 합니다 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 는 스키마의 필드 목록을 사용하도록 업데이트됩니다(결과를 얻을 때 확장할 항목의 이름을 알기 위해). 하지만 실제 스키마 적용은 다음과 같습니다 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 는 필드를 텍스트 목록({text})으로 설정합니다Emails. 형식을 올바르게 적용하는 경우 이제 단위 테스트에서 Type.ListItem에 대한 호출이 대신 type any반환 type text 되어야 합니다.

단위 테스트를 다시 실행하면 이제 모두 통과하고 있음을 보여 줍니다.

성공한 단위 테스트입니다.

일반 코드를 별도의 파일로 리팩터링

참고 항목

M 엔진은 나중에 외부 모듈/일반 코드를 참조하는 지원이 향상될 예정이지만, 이 방법은 그때까지 계속 진행되어야 합니다.

이 시점에서 확장에는 TripPin 커넥터 코드만큼 "일반적인" 코드가 거의 있습니다. 나중에 이러한 일반적인 함수는 기본 제공 표준 함수 라이브러리의 일부가 되거나 다른 확장에서 참조할 수 있습니다. 지금은 다음과 같은 방법으로 코드를 리팩터링합니다.

  1. 재사용 가능한 함수를 별도의 파일(.pqm)으로 이동합니다.
  2. 파일의 빌드 작업 속성을 컴파일설정하여 빌드하는 동안 확장 파일에 포함되도록 합니다.
  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");

결론

이 자습서에서는 REST API에서 가져오는 데이터에 스키마를 적용하는 방법을 여러 가지 개선했습니다. 커넥터는 현재 런타임에 성능 이점이 있지만 서비스의 메타데이터 초과 작업 변경에 적응할 수 없는 스키마 정보를 코딩하기 어렵습니다. 이후 자습서는 서비스의 $metadata 문서에서 스키마를 유추하는 순수한 동적 접근 방식으로 이동합니다.

이 자습서에서는 스키마 변경 외에도 코드에 대한 단위 테스트를 추가하고 일반적인 도우미 함수를 별도의 파일로 리팩터링하여 전반적인 가독성을 향상시켰습니다.

다음 단계

TripPin 8부 - 진단 추가