Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Observação
Atualmente, esse conteúdo faz referência ao conteúdo de uma implementação herdada para teste de unidade no Visual Studio. O conteúdo será atualizado no futuro para abranger a nova estrutura de teste do SDK do Power Query.
Este tutorial de várias partes aborda a criação de uma nova extensão de fonte de dados para o Power Query. O tutorial deve ser feito sequencialmente — cada lição se baseia no conector criado nas lições anteriores, adicionando progressivamente novos recursos ao conector.
Nesta lição, você:
- Impor um esquema de tabela usando M Types
- Definir tipos para registros e listas aninhados
- Refatorar código para reutilização e teste de unidade
Na lição anterior, você definiu seus esquemas de tabela usando um sistema simples de "Tabela de Esquema". Essa abordagem de tabela de esquema funciona para muitas APIs REST/Conectores de Dados. Mas os serviços que retornam conjuntos de dados completos ou profundamente aninhados podem se beneficiar da abordagem neste tutorial, que usa o sistema de tipos M.
Esta lição orienta você pelas seguintes etapas:
- Adicionando testes de unidade.
- Definindo tipos M personalizados.
- Impondo um esquema usando tipos.
- Refatorando código comum em arquivos separados.
Adicionando testes de unidade
Antes de começar a usar a lógica de esquema avançada, você precisa adicionar um conjunto de testes de unidade ao conector para reduzir a chance de quebrar algo inadvertidamente. O teste de unidade funciona da seguinte maneira:
- Copie o código comum do exemplo UnitTest para o
TripPin.query.pqarquivo. - Adicione uma declaração de seção à parte superior do
TripPin.query.pqarquivo. - Criar um registro compartilhado (chamado
TripPin.UnitTest). - Defina um
Factpara cada teste. - Chame
Facts.Summarize()para executar todos os testes. - Faça referência à chamada anterior como o valor compartilhado para garantir que ela seja avaliada quando o projeto for executado no 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];
Selecionar execução no projeto avalia todos os fatos e gera uma saída de relatório semelhante a esta:
Usando alguns princípios do desenvolvimento controlado por teste, agora você adiciona um teste que falha no momento, mas pode ser reimplementado e corrigido em breve (até o final deste tutorial). Especificamente, você adiciona um teste que verifica um dos registros aninhados (Emails) que você recebe na entidade Pessoas.
Fact("Emails is properly typed", type text, Type.ListItem(Value.Type(People{0}[Emails])))
Se você executar o código novamente, agora verá que tem um teste com falha.
Agora você só precisa implementar a funcionalidade para fazer isso funcionar.
Definindo tipos de M personalizados
A abordagem de imposição de esquema na lição anterior usou "tabelas de esquema" definidas como pares Nome/Tipo. Ele funciona bem ao trabalhar com dados achatados/relacionais, mas não dá suporte à configuração de tipos em registros/tabelas/listas aninhadas, nem permite reutilizar definições de tipos entre tabelas/entidades.
No caso TripPin, os dados nas entidades Pessoas e Aeroportos contêm colunas estruturadas e até compartilham um tipo (Location) para representar informações de endereço. Em vez de definir pares nome/tipo em uma tabela de esquema, você precisa definir cada uma dessas entidades usando declarações de tipo M personalizadas.
Aqui está uma revisão rápida sobre tipos na linguagem M da Especificação de Linguagem.
Um valor de tipo é um valor que classifica outros valores. Um valor classificado por um tipo obedece a esse tipo. O sistema de tipos de M é composto pelas seguintes categorias de tipos:
- Tipos primitivos, que classificam valores primitivos (
binary, ,date,datetime,datetimezone,duration,list,logical, ,null,number,record, ,text,timetype) e também incluem uma série de tipos abstratos (function,table, eanynone) - Tipos de registro, que classificam valores de registro com base em nomes de campo e em tipos de valor
- Tipos de lista, que classificam listas usando apenas um tipo de base de item
- Tipos de função, que classificam valores de função com base nos tipos de seus parâmetros e valores de retorno
- Tipos de tabela, que classificam valores de tabela com base em nomes de coluna, tipos de coluna e chaves
- Tipos anuláveis, que classificam o valor nulo, além de todos os valores classificados por um tipo base
- Tipos de tipo, que classificam valores que são tipos
Usando a saída JSON bruta que você obtém (e/ou pesquisando as definições no $metadata do serviço), você pode definir os seguintes tipos de registro para representar tipos complexos 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
];
Observe como o LocationType faz referência ao CityType e ao LocType para representar suas colunas estruturadas.
Para as entidades de nível superior (que você deseja representar como Tabelas), defina os tipos de tabela:
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
];
Em seguida, atualize sua SchemaTable variável (que você usa como uma "tabela de pesquisa" para que a entidade digite mapeamentos) para usar essas novas definições de tipo:
SchemaTable = #table({"Entity", "Type"}, {
{"Airlines", AirlinesType },
{"Airports", AirportsType },
{"People", PeopleType}
});
Impor um esquema usando tipos
Você depende de uma função comum (Table.ChangeType) para impor um esquema em seus dados, assim como você usou SchemaTransformTable na lição anterior. Ao contrário de SchemaTransformTable, Table.ChangeType usa um tipo de tabela M real como um argumento e aplica seu esquema recursivamente para todos os tipos aninhados. Sua assinatura tem esta aparência:
Table.ChangeType = (table, tableType as type) as nullable table => ...
A listagem de código completa para a Table.ChangeType função pode ser encontrada no arquivo Table.ChangeType.pqm .
Observação
Para flexibilidade, a função pode ser usada em tabelas, bem como listas de registros (que é como as tabelas seriam representadas em um documento JSON).
Em seguida, você precisa atualizar o código do conector para alterar o parâmetro de table para type, e adicionar uma chamada para Table.ChangeType em 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 é atualizado para usar a lista de campos do esquema (para saber os nomes do que expandir quando você obtém os resultados), mas deixa a imposição de esquema real para 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];
Confirmando que os tipos aninhados estão sendo definidos
A definição para o seu PeopleType agora define o Emails campo como uma lista de texto ({text}). Se você estiver aplicando os tipos corretamente, a chamada para Type.ListItem no teste de unidade agora deverá estar retornando type text em vez de type any.
Executar novamente os testes de unidade mostra que agora todos passam.
Captura de tela da guia de saída da consulta M mostrando os testes de unidade bem-sucedidos.
Refatorando código comum em arquivos separados
Observação
O mecanismo M terá suporte aprimorado para referenciar módulos externos/código comum no futuro, mas essa abordagem deve levá-lo até lá.
Neste ponto, sua extensão quase tem tanto código "comum" quanto o código do conector TripPin. No futuro, essas funções comuns farão parte da biblioteca de funções padrão interna ou você poderá referenciá-las de outra extensão. Por enquanto, você refatora seu código da seguinte maneira:
- Mova as funções reutilizáveis para arquivos separados (.pqm).
- Defina a propriedade Ação de Build no arquivo para Compilar para garantir que ela seja incluída no arquivo de extensão durante o build.
- Defina uma função para carregar o código usando Expression.Evaluate.
- Carregue cada uma das funções comuns que você deseja usar.
O código para fazer essa refatoração está incluído no seguinte snippet de código:
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");
Conclusion
Este tutorial fez uma série de melhorias na maneira como você impõe um esquema nos dados obtidos de uma API REST. No momento, o conector está definindo de forma fixa suas informações de esquema, o que tem um benefício de desempenho em tempo de execução, mas não consegue se adaptar às alterações nos metadados do serviço ao longo do tempo. Os tutoriais futuros passarão para uma abordagem puramente dinâmica que inferirá o esquema do documento $metadata do serviço.
Além das alterações de esquema, este tutorial adicionou testes de unidade para seu código e refatorou as funções auxiliares comuns em arquivos separados para melhorar a legibilidade geral.