Bagikan melalui


TripPin bagian 10 - Pelipatan kueri dasar

Catatan

Konten ini saat ini mereferensikan konten dari implementasi warisan untuk log di Visual Studio. Konten akan diperbarui dalam waktu dekat untuk mencakup Power Query SDK baru di Visual Studio Code.

Tutorial multi-bagian ini mencakup pembuatan ekstensi sumber data baru untuk Power Query. Tutorial ini dimaksudkan untuk dilakukan secara berurutan—setiap pelajaran dibangun pada konektor yang dibuat dalam pelajaran sebelumnya, secara bertahap menambahkan kemampuan baru ke konektor Anda.

Dalam pelajaran ini, Anda akan:

  • Pelajari dasar-dasar pelipatan kueri
  • Pelajari tentang fungsi Table.View
  • Replikasi penanganan pelipatan kueri OData untuk:
  • $top
  • $skip
  • $count
  • $select
  • $orderby

Salah satu fitur canggih dari bahasa M adalah kemampuannya untuk mendorong pekerjaan transformasi ke satu atau beberapa sumber data yang mendasar. Kemampuan ini disebut sebagai Pelipatan Kueri (alat/teknologi lain juga merujuk ke fungsi serupa sebagai Predikat Pushdown, atau Delegasi Kueri).

Saat membuat konektor kustom yang menggunakan fungsi M dengan kemampuan pelipatan kueri bawaan, seperti OData.Feed atau Odbc.DataSource, konektor Anda secara otomatis mewarisi kemampuan ini secara gratis.

Tutorial ini mereplikasi perilaku pelipatan kueri bawaan untuk OData dengan menerapkan penangan fungsi untuk fungsi Table.View . Bagian tutorial ini mengimplementasikan beberapa handler yang lebih mudah diimplementasikan (yaitu, yang tidak memerlukan penguraian ekspresi dan pelacakan status).

Untuk memahami selengkapnya tentang kemampuan kueri yang mungkin ditawarkan layanan OData, buka Konvensi URL OData v4.

Catatan

Seperti yang dinyatakan sebelumnya, fungsi OData.Feed secara otomatis menyediakan kemampuan lipatan kueri. Karena seri TripPin memperlakukan layanan OData sebagai REST API biasa, menggunakan Web.Contents daripada OData.Feed, Anda harus mengimplementasikan penangan lipat kueri sendiri. Untuk penggunaan dunia nyata, kami sarankan Anda menggunakan OData.Feed jika memungkinkan.

Buka Gambaran Umum evaluasi kueri dan pelipatan kueri di Power Query untuk informasi selengkapnya tentang pelipatan kueri.

Menggunakan Table.View

Fungsi Table.View memungkinkan konektor kustom untuk mengambil alih penangan transformasi default untuk sumber data Anda. Implementasi Table.View akan menyediakan fungsi untuk satu atau beberapa handler yang didukung. Jika handler tidak terbendung, atau mengembalikan error selama evaluasi, mesin M kembali ke handler defaultnya.

Saat konektor kustom menggunakan fungsi yang tidak mendukung pelipatan kueri implisit, seperti Web.Contents, penanganan transformasi default selalu dilakukan secara lokal. Jika REST API yang Anda sambungkan mendukung parameter kueri sebagai bagian dari kueri, Table.View memungkinkan Anda menambahkan pengoptimalan yang memungkinkan pekerjaan transformasi didorong ke layanan.

Fungsi Table.View memiliki tanda tangan berikut:

Table.View(table as nullable table, handlers as record) as table

Implementasi Anda membungkus fungsi sumber data utama Anda. Ada dua handler yang diperlukan untuk Table.View:

  • GetType—mengembalikan hasil kueri yang diharapkan table type
  • GetRows—mengembalikan hasil aktual table fungsi sumber data Anda

Implementasi paling sederhana akan mirip dengan contoh berikut:

TripPin.SuperSimpleView = (url as text, entity as text) as table =>
    Table.View(null, [
        GetType = () => Value.Type(GetRows()),
        GetRows = () => GetEntity(url, entity)
    ]);

TripPinNavTable Perbarui fungsi untuk memanggil TripPin.SuperSimpleView daripada GetEntity:

withData = Table.AddColumn(rename, "Data", each TripPin.SuperSimpleView(url, [Name]), type table),

Jika Anda menjalankan ulang pengujian unit, Anda akan melihat bahwa perilaku fungsi Anda tidak diubah. Dalam hal ini, implementasi Table.View Anda hanya melewati panggilan ke GetEntity. Karena Anda belum menerapkan handler transformasi apa pun (belum), parameter asli url tetap tidak tersentuh.

Implementasi awal Table.View

Implementasi Table.View di atas sederhana, tetapi tidak terlalu berguna. Implementasi berikut digunakan sebagai garis besar Anda—tidak mengimplementasikan fungsionalitas lipatan apa pun, tetapi memiliki perancah yang Anda butuhkan untuk melakukannya.

TripPin.View = (baseUrl as text, entity as text) as table =>
    let
        // Implementation of Table.View handlers.
        //
        // We wrap the record with Diagnostics.WrapHandlers() to get some automatic
        // tracing if a handler returns an error.
        //
        View = (state as record) => Table.View(null, Diagnostics.WrapHandlers([
            // Returns the table type returned by GetRows()
            GetType = () => CalculateSchema(state),

            // Called last - retrieves the data from the calculated URL
            GetRows = () => 
                let
                    finalSchema = CalculateSchema(state),
                    finalUrl = CalculateUrl(state),

                    result = TripPin.Feed(finalUrl, finalSchema),
                    appliedType = Table.ChangeType(result, finalSchema)
                in
                    appliedType,

            //
            // Helper functions
            //
            // Retrieves the cached schema. If this is the first call
            // to CalculateSchema, the table type is calculated based on
            // the entity name that was passed into the function.
            CalculateSchema = (state) as type =>
                if (state[Schema]? = null) then
                    GetSchemaForEntity(entity)
                else
                    state[Schema],

            // Calculates the final URL based on the current state.
            CalculateUrl = (state) as text => 
                let
                    urlWithEntity = Uri.Combine(state[Url], state[Entity])
                in
                    urlWithEntity
        ]))
    in
        View([Url = baseUrl, Entity = entity]);

Jika Anda melihat panggilan ke Table.View, Anda akan melihat fungsi pembungkus ekstra di handlers sekitar rekaman—Diagnostics.WrapHandlers. Fungsi pembantu ini ditemukan dalam modul Diagnostik (yang diperkenalkan dalam menambahkan pelajaran diagnostik ), dan memberi Anda cara yang berguna untuk secara otomatis melacak kesalahan apa pun yang dimunculkan oleh penangan individual.

Fungsi GetType dan GetRows diperbarui untuk menggunakan dua fungsi pembantu baru —CalculateSchema dan CalculateUrl. Saat ini, implementasi fungsi-fungsi tersebut cukup mudah —perhatikan bahwa fungsi tersebut berisi bagian dari apa yang sebelumnya dilakukan oleh GetEntity fungsi.

Terakhir, perhatikan bahwa Anda menentukan fungsi internal (View) yang menerima state parameter. Saat Anda mengimplementasikan lebih banyak handler, mereka akan secara rekursif memanggil fungsi internal View , memperbarui, dan meneruskannya state saat dijalankan.

TripPinNavTable Perbarui fungsi sekali lagi, ganti panggilan ke TripPin.SuperSimpleView dengan panggilan ke fungsi baruTripPin.View, dan jalankan ulang pengujian unit. Anda belum akan melihat fungsionalitas baru, tetapi Sekarang Anda memiliki garis besar yang solid untuk pengujian.

Menerapkan pelipatan kueri

Karena mesin M secara otomatis kembali ke pemrosesan lokal saat kueri tidak dapat dilipat, Anda harus mengambil beberapa langkah tambahan untuk memvalidasi bahwa handler Table.View Anda berfungsi dengan benar.

Cara manual untuk memvalidasi perilaku lipatan adalah dengan menonton permintaan URL yang dibuat pengujian unit Anda menggunakan alat seperti Fiddler. Atau, pembuatan log diagnostik yang Anda tambahkan untuk TripPin.Feed memancarkan URL lengkap yang dijalankan, yang harus menyertakan parameter string kueri OData yang ditambahkan handler Anda.

Cara otomatis untuk memvalidasi pelipatan kueri adalah dengan memaksa eksekusi pengujian unit Anda gagal jika kueri tidak sepenuhnya terlipat. Anda dapat melakukan ini dengan membuka properti proyek, dan mengatur Kesalahan pada Kegagalan Lipatan ke True. Dengan pengaturan ini diaktifkan, kueri apa pun yang memerlukan pemrosesan lokal menghasilkan kesalahan berikut:

Kami tidak dapat melipat ekspresi ke sumbernya. Silakan coba ekspresi yang lebih sederhana.

Anda dapat menguji ini dengan menambahkan yang baru Fact ke file pengujian unit Anda yang berisi satu atau beberapa transformasi tabel.

// Query folding tests
Fact("Fold $top 1 on Airlines", 
    #table( type table [AirlineCode = text, Name = text] , {{"AA", "American Airlines"}} ), 
    Table.FirstN(Airlines, 1)
)

Catatan

Pengaturan Kesalahan pada Kegagalan Lipatan adalah pendekatan "semua atau tidak sama sekali". Jika Anda ingin menguji kueri yang tidak dirancang untuk dilipat sebagai bagian dari pengujian unit, Anda harus menambahkan beberapa logika kondisional untuk mengaktifkan/menonaktifkan pengujian yang sesuai.

Bagian yang tersisa dari tutorial ini masing-masing menambahkan handler Table.View baru. Anda mengambil pendekatan Test Driven Development (TDD), tempat Anda pertama kali menambahkan pengujian unit yang gagal, lalu menerapkan kode M untuk mengatasinya.

Bagian handler berikut menjelaskan fungsionalitas yang disediakan oleh handler, sintaks kueri setara OData, pengujian unit, dan implementasinya. Menggunakan kode perancah yang dijelaskan sebelumnya, setiap implementasi handler memerlukan dua perubahan:

  • Menambahkan handler ke Table.View yang memperbarui state rekaman.
  • Memodifikasi CalculateUrl untuk mengambil nilai dari state dan menambahkan ke parameter string url dan/atau kueri.

Menangani Table.FirstN dengan OnTake

Handler OnTake menerima count parameter, yang merupakan jumlah maksimum baris yang akan diambil dari GetRows. Dalam istilah OData, Anda dapat menerjemahkan ini ke parameter kueri $top .

Anda menggunakan pengujian unit berikut:

// Query folding tests
Fact("Fold $top 1 on Airlines", 
    #table( type table [AirlineCode = text, Name = text] , {{"AA", "American Airlines"}} ), 
    Table.FirstN(Airlines, 1)
),
Fact("Fold $top 0 on Airports", 
    #table( type table [Name = text, IataCode = text, Location = record] , {} ), 
    Table.FirstN(Airports, 0)
),

Pengujian ini keduanya menggunakan Table.FirstN untuk memfilter ke hasil yang diatur ke jumlah baris X pertama. Jika Anda menjalankan pengujian ini dengan Kesalahan pada Kegagalan Lipatan diatur ke False (default), pengujian harus berhasil, tetapi jika Anda menjalankan Fiddler (atau memeriksa log jejak), perhatikan bahwa permintaan yang Anda kirim tidak berisi parameter kueri OData apa pun.

Jejak diagnostik.

Jika Anda mengatur Kesalahan pada Kegagalan Pelipatan ke True, pengujian gagal dengan Please try a simpler expression. kesalahan. Untuk memperbaiki kesalahan ini, Anda perlu menentukan handler Table.View pertama Anda untuk OnTake.

Handler OnTake terlihat seperti kode berikut:

OnTake = (count as number) =>
    let
        // Add a record with Top defined to our state
        newState = state & [ Top = count ]
    in
        @View(newState),

Fungsi ini CalculateUrl diperbarui untuk mengekstrak Top nilai dari state rekaman, dan mengatur parameter yang tepat dalam string kueri.

// Calculates the final URL based on the current state.
CalculateUrl = (state) as text => 
    let
        urlWithEntity = Uri.Combine(state[Url], state[Entity]),

        // Uri.BuildQueryString requires that all field values
        // are text literals.
        defaultQueryString = [],

        // Check for Top defined in our state
        qsWithTop =
            if (state[Top]? <> null) then
                // add a $top field to the query string record
                defaultQueryString & [ #"$top" = Number.ToText(state[Top]) ]
            else
                defaultQueryString,

        encodedQueryString = Uri.BuildQueryString(qsWithTop),
        finalUrl = urlWithEntity & "?" & encodedQueryString
    in
        finalUrl

Menjalankan ulang pengujian unit, perhatikan bahwa URL yang Anda akses sekarang berisi $top parameter . Karena pengodean URL, $top muncul sebagai %24top, tetapi layanan OData cukup pintar untuk mengonversinya secara otomatis.

Jejak diagnostik dengan atas.

Menangani Table.Skip dengan OnSkip

Handler OnSkip sangat mirip OnTake. Ini menerima count parameter, yang merupakan jumlah baris yang akan dilewati dari kumpulan hasil. Handler ini diterjemahkan dengan baik ke parameter kueri $skip OData.

Pengujian unit:

// OnSkip
Fact("Fold $skip 14 on Airlines",
    #table( type table [AirlineCode = text, Name = text] , {{"EK", "Emirates"}} ), 
    Table.Skip(Airlines, 14)
),
Fact("Fold $skip 0 and $top 1",
    #table( type table [AirlineCode = text, Name = text] , {{"AA", "American Airlines"}} ),
    Table.FirstN(Table.Skip(Airlines, 0), 1)
),

Implementasi:

// OnSkip - handles the Table.Skip transform.
// The count value should be >= 0.
OnSkip = (count as number) =>
    let
        newState = state & [ Skip = count ]
    in
        @View(newState),

Mencocokkan pembaruan ke CalculateUrl:

qsWithSkip = 
    if (state[Skip]? <> null) then
        qsWithTop & [ #"$skip" = Number.ToText(state[Skip]) ]
    else
        qsWithTop,

Informasi selengkapnya: Table.Skip

Menangani Table.SelectColumns dengan OnSelectColumns

Handler OnSelectColumns dipanggil saat pengguna memilih atau menghapus kolom dari kumpulan hasil. Handler menerima list nilai text , mewakili satu atau beberapa kolom yang akan dipilih.

Dalam istilah OData, operasi ini memetakan ke opsi kueri $select .

Keuntungan pemilihan kolom lipat menjadi jelas saat Anda berhadapan dengan tabel dengan banyak kolom. Operator $select menghapus kolom yang tidak dipilih dari kumpulan hasil, menghasilkan kueri yang lebih efisien.

Pengujian unit:

// OnSelectColumns
Fact("Fold $select single column", 
    #table( type table [AirlineCode = text] , {{"AA"}} ),
    Table.FirstN(Table.SelectColumns(Airlines, {"AirlineCode"}), 1)
),
Fact("Fold $select multiple column", 
    #table( type table [UserName = text, FirstName = text, LastName = text],{{"russellwhyte", "Russell", "Whyte"}}), 
    Table.FirstN(Table.SelectColumns(People, {"UserName", "FirstName", "LastName"}), 1)
),
Fact("Fold $select with ignore column", 
    #table( type table [AirlineCode = text] , {{"AA"}} ),
    Table.FirstN(Table.SelectColumns(Airlines, {"AirlineCode", "DoesNotExist"}, MissingField.Ignore), 1)
),

Dua pengujian pertama memilih jumlah kolom yang berbeda dengan Table.SelectColumns, dan menyertakan panggilan Table.FirstN untuk menyederhanakan kasus pengujian.

Catatan

Jika pengujian hanya mengembalikan nama kolom (menggunakan Table.ColumnNames dan bukan data apa pun, permintaan ke layanan OData tidak akan pernah benar-benar dikirim. Ini karena panggilan ke GetType akan mengembalikan skema, yang berisi semua informasi yang dibutuhkan mesin M untuk menghitung hasilnya.

Pengujian ketiga menggunakan opsi MissingField.Ignore , yang memberi tahu mesin M untuk mengabaikan kolom yang dipilih yang tidak ada dalam tataan hasil. Handler OnSelectColumns tidak perlu khawatir tentang opsi ini—mesin M menanganinya secara otomatis (artinya, kolom yang hilang tidak disertakan dalam columns daftar).

Catatan

Opsi lain untuk Table.SelectColumns, MissingField.UseNull, memerlukan konektor untuk mengimplementasikan OnAddColumn handler. Ini akan dilakukan dalam pelajaran berikutnya.

Implementasi untuk OnSelectColumns melakukan dua hal:

  • Menambahkan daftar kolom yang dipilih ke state.
  • Menghitung Schema ulang nilai sehingga Anda dapat mengatur jenis tabel yang tepat.
OnSelectColumns = (columns as list) =>
    let
        // get the current schema
        currentSchema = CalculateSchema(state),
        // get the columns from the current schema (which is an M Type value)
        rowRecordType = Type.RecordFields(Type.TableRow(currentSchema)),
        existingColumns = Record.FieldNames(rowRecordType),
        // calculate the new schema
        columnsToRemove = List.Difference(existingColumns, columns),
        updatedColumns = Record.RemoveFields(rowRecordType, columnsToRemove),
        newSchema = type table (Type.ForRecord(updatedColumns, false))
    in
        @View(state & 
            [ 
                SelectColumns = columns,
                Schema = newSchema
            ]
        ),

CalculateUrl diperbarui untuk mengambil daftar kolom dari status, dan menggabungkannya (dengan pemisah) untuk $select parameter .

// Check for explicitly selected columns
qsWithSelect =
    if (state[SelectColumns]? <> null) then
        qsWithSkip & [ #"$select" = Text.Combine(state[SelectColumns], ",") ]
    else
        qsWithSkip,

Menangani Table.Sort dengan OnSort

Handler OnSort menerima daftar rekaman jenis:

type [ Name = text, Order = Int16.Type ]

Setiap rekaman berisi Name bidang, menunjukkan nama kolom, dan Order bidang yang sama dengan Order.Ascending atau Order.Descending.

Dalam istilah OData, operasi ini memetakan ke opsi kueri $orderby . $orderby Sintaks memiliki nama kolom diikuti oleh asc atau desc untuk menunjukkan urutan naik atau turun. Saat Anda mengurutkan pada beberapa kolom, nilai dipisahkan dengan koma. columns Jika parameter berisi lebih dari satu item, penting untuk mempertahankan urutan tampilannya.

Pengujian unit:

// OnSort
Fact("Fold $orderby single column",
    #table( type table [AirlineCode = text, Name = text], {{"TK", "Turkish Airlines"}}),
    Table.FirstN(Table.Sort(Airlines, {{"AirlineCode", Order.Descending}}), 1)
),
Fact("Fold $orderby multiple column",
    #table( type table [UserName = text], {{"javieralfred"}}),
    Table.SelectColumns(Table.FirstN(Table.Sort(People, {{"LastName", Order.Ascending}, {"UserName", Order.Descending}}), 1), {"UserName"})
)

Implementasi:

// OnSort - receives a list of records containing two fields: 
//    [Name]  - the name of the column to sort on
//    [Order] - equal to Order.Ascending or Order.Descending
// If there are multiple records, the sort order must be maintained.
//
// OData allows you to sort on columns that do not appear in the result
// set, so we do not have to validate that the sorted columns are in our 
// existing schema.
OnSort = (order as list) =>
    let
        // This will convert the list of records to a list of text,
        // where each entry is "<columnName> <asc|desc>"
        sorting = List.Transform(order, (o) => 
            let
                column = o[Name],
                order = o[Order],
                orderText = if (order = Order.Ascending) then "asc" else "desc"
            in
                column & " " & orderText
        ),
        orderBy = Text.Combine(sorting, ", ")
    in
        @View(state & [ OrderBy = orderBy ]),

Pembaruan untuk CalculateUrl:

qsWithOrderBy = 
    if (state[OrderBy]? <> null) then
        qsWithSelect & [ #"$orderby" = state[OrderBy] ]
    else
        qsWithSelect,

Menangani Table.RowCount dengan GetRowCount

Tidak seperti handler kueri lain yang Anda terapkan, GetRowCount handler mengembalikan satu nilai—jumlah baris yang diharapkan dalam tataan hasil. Dalam kueri M, nilai ini biasanya akan menjadi hasil transformasi Table.RowCount .

Anda memiliki beberapa opsi berbeda tentang cara menangani nilai ini sebagai bagian dari kueri OData:

  • Parameter kueri $count, yang mengembalikan hitungan sebagai bidang terpisah dalam tataan hasil.
  • Segmen jalur /$count, yang hanya mengembalikan jumlah total, sebagai nilai skalar.

Kelemahan pendekatan parameter kueri adalah Anda masih perlu mengirim seluruh kueri ke layanan OData. Karena hitungan kembali sebaris sebagai bagian dari kumpulan hasil, Anda harus memproses halaman pertama data dari kumpulan hasil. Meskipun proses ini masih lebih efisien daripada membaca seluruh tataan hasil dan menghitung baris, proses ini mungkin masih lebih banyak pekerjaan daripada yang ingin Anda lakukan.

Keuntungan dari pendekatan segmen jalur adalah Anda hanya menerima satu nilai skalar dalam hasilnya. Pendekatan ini membuat seluruh operasi jauh lebih efisien. Namun, seperti yang dijelaskan dalam spesifikasi OData, segmen jalur /$count mengembalikan kesalahan jika Anda menyertakan parameter kueri lain, seperti $top atau $skip, yang membatasi kegunaannya.

Dalam tutorial ini, Anda menerapkan GetRowCount handler menggunakan pendekatan segmen jalur. Untuk menghindari kesalahan yang akan Anda dapatkan jika parameter kueri lain disertakan, Anda memeriksa nilai status lainnya, dan mengembalikan "kesalahan yang tidak diimpikan" (...) jika Anda menemukannya. Mengembalikan kesalahan apa pun dari handler Table.View memberi tahu mesin M bahwa operasi tidak dapat dilipat, dan harus kembali ke handler default sebagai gantinya (yang dalam hal ini akan menghitung jumlah total baris).

Pertama, tambahkan pengujian unit:

// GetRowCount
Fact("Fold $count", 15, Table.RowCount(Airlines)),

/$count Karena segmen jalur mengembalikan nilai tunggal (dalam format biasa/teks) daripada tataan hasil JSON, Anda juga harus menambahkan fungsi internal baru (TripPin.Scalar) untuk membuat permintaan dan menangani hasilnya.

// Similar to TripPin.Feed, but is expecting back a scalar value.
// This function returns the value from the service as plain text.
TripPin.Scalar = (url as text) as text =>
    let
        _url = Diagnostics.LogValue("TripPin.Scalar url", url),

        headers = DefaultRequestHeaders & [
            #"Accept" = "text/plain"
        ],

        response = Web.Contents(_url, [ Headers = headers ]),
        toText = Text.FromBinary(response)
    in
        toText;

Implementasi kemudian menggunakan fungsi ini (jika tidak ada parameter kueri lain yang ditemukan dalam state):

GetRowCount = () as number =>
    if (Record.FieldCount(Record.RemoveFields(state, {"Url", "Entity", "Schema"}, MissingField.Ignore)) > 0) then
        ...
    else
        let
            newState = state & [ RowCountOnly = true ],
            finalUrl = CalculateUrl(newState),
            value = TripPin.Scalar(finalUrl),
            converted = Number.FromText(value)
        in
            converted,

Fungsi CalculateUrl diperbarui untuk menambahkan /$count ke URL jika RowCountOnly bidang diatur dalam state.

// Check for $count. If all we want is a row count,
// then we add /$count to the path value (following the entity name).
urlWithRowCount =
    if (state[RowCountOnly]? = true) then
        urlWithEntity & "/$count"
    else
        urlWithEntity,

Pengujian unit baru Table.RowCount sekarang harus lulus.

Untuk menguji kasus fallback, Anda menambahkan pengujian lain yang memaksa kesalahan.

Pertama, tambahkan metode pembantu try yang memeriksa hasil operasi untuk kesalahan pelipatan.

// Returns true if there is a folding error, or the original record (for logging purposes) if not.
Test.IsFoldingError = (tryResult as record) =>
    if ( tryResult[HasError]? = true and tryResult[Error][Message] = "We couldn't fold the expression to the data source. Please try a simpler expression.") then
        true
    else
        tryResult;

Kemudian tambahkan pengujian yang menggunakan Table.RowCount dan Table.FirstN untuk memaksa kesalahan.

// test will fail if "Fail on Folding Error" is set to false
Fact("Fold $count + $top *error*", true, Test.IsFoldingError(try Table.RowCount(Table.FirstN(Airlines, 3)))),

Catatan penting di sini adalah bahwa pengujian ini sekarang mengembalikan kesalahan jika Kesalahan pada Kesalahan Lipatan diatur ke false, karena Table.RowCount operasi kembali ke handler lokal (default). Menjalankan pengujian dengan Kesalahan pada Kesalahan Lipatan diatur ke true penyebab Table.RowCount gagal, dan memungkinkan pengujian berhasil.

Kesimpulan

Menerapkan Table.View untuk konektor Anda menambahkan sejumlah besar kompleksitas ke kode Anda. Karena mesin M dapat memproses semua transformasi secara lokal, menambahkan handler Table.View tidak mengaktifkan skenario baru untuk pengguna Anda, tetapi menghasilkan pemrosesan yang lebih efisien (dan pengguna yang berpotensi lebih bahagia). Salah satu keuntungan utama handler Table.View bersifat opsional adalah memungkinkan Anda menambahkan fungsionalitas baru secara bertahap tanpa memengaruhi kompatibilitas mundur untuk konektor Anda.

Untuk sebagian besar konektor, handler penting (dan dasar) untuk diimplementasikan adalah OnTake (yang diterjemahkan ke $top dalam OData), karena membatasi jumlah baris yang dikembalikan. Pengalaman Power Query selalu melakukan baris OnTake1000 saat menampilkan pratinjau di navigator dan editor kueri, sehingga pengguna Anda mungkin melihat peningkatan performa yang signifikan saat bekerja dengan himpunan data yang lebih besar.