Freigeben über


TripPin Teil 10 – Grundlagen der Abfragefaltung

Hinweis

Dieser Inhalt verweist derzeit auf Inhalte aus einer Legacyimplementierung für Protokolle in Visual Studio. Der Inhalt wird in Naher Zukunft aktualisiert, um das neue Power Query SDK in Visual Studio Code abzudecken.

In diesem mehrteiligen Lernprogramm wird die Erstellung einer neuen Datenquellenerweiterung für Power Query behandelt. Das Lernprogramm soll sequenziell durchgeführt werden – jede Lektion baut auf dem connector auf, der in früheren Lektionen erstellt wurde, und fügt Ihrem Connector inkrementell neue Funktionen hinzu.

In dieser Lektion lernen Sie:

  • Lernen Sie die Grundlagen der Abfragefaltung kennen
  • Informationen zur Funktion "Table.View"
  • Repliziere OData-Abfragefaltungs-Handler für:
  • $top
  • $skip
  • $count
  • $select
  • $orderby

Eines der leistungsstarken Features der M-Sprache ist die Möglichkeit, Transformationen an eine oder mehrere zugrunde liegende Datenquellen zu übertragen. Diese Funktion wird als Abfragefaltung bezeichnet (andere Tools/Technologien beziehen sich auch auf ähnliche Funktionen wie Prädikat-Pushdown oder Abfragedelegierung).

Beim Erstellen eines benutzerdefinierten Connectors, der eine M-Funktion mit integrierten Abfragefaltungsfunktionen verwendet, z. B. OData.Feed oder Odbc.DataSource, erbt Ihr Connector diese Funktion automatisch kostenlos.

In diesem Lernprogramm wird das integrierte Abfragefaltungsverhalten für OData repliziert, indem Funktionshandler für die Table.View-Funktion implementiert werden. In diesem Teil des Lernprogramms werden einige der einfacheren Handler implementiert (d. h. solche, die keine Ausdrucksanalyse und Zustandsnachverfolgung erfordern).

Um mehr über die Abfragefunktionen zu erfahren, die ein OData-Dienst möglicherweise anbieten kann, wechseln Sie zu OData v4-URL-Konventionen.

Hinweis

Wie bereits erwähnt, bietet die OData.Feed-Funktion automatisch Abfragefaltungsfunktionen. Da die TripPin-Reihe den OData-Service als normale REST-API behandelt und Web.Contents anstelle von OData.Feed verwendet, müssen Sie die Abfragefaltungshandler selbst implementieren. Für die reale Nutzung wird empfohlen, OData.Feed nach Möglichkeit zu verwenden.

Wechseln Sie zur Übersicht über die Abfrageauswertung und Abfragefaltung in Power Query , um weitere Informationen zur Abfragefaltung zu erhalten.

Verwenden von Table.View

Die Table.View-Funktion ermöglicht es einem benutzerdefinierten Connector, Standardtransformationshandler für Ihre Datenquelle außer Kraft zu setzen. Eine Implementierung von Table.View stellt eine Funktion für einen oder mehrere der unterstützten Handler bereit. Wenn ein Handler nicht implementiert ist oder während der Auswertung ein error zurückgibt, greift die M-Engine auf ihren Standardhandler zurück.

Wenn ein benutzerdefinierter Connector eine Funktion verwendet, die keine implizite Abfragefaltung unterstützt, z. B. Web.Contents, werden Standardtransformationshandler immer lokal ausgeführt. Wenn die REST-API, mit der Sie eine Verbindung herstellen, Abfrageparameter als Teil der Abfrage unterstützt, können Sie mit Table.View Optimierungen hinzufügen, mit denen Transformationsarbeiten an den Dienst übertragen werden können.

Die Funktion "Table.View" weist die folgende Signatur auf:

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

Ihre Implementierung umschließt die Hauptdatenquellenfunktion. Es gibt zwei erforderliche Handler für Table.View:

  • GetType: Gibt das erwartete table type Abfrageergebnis zurück.
  • GetRows: Gibt das tatsächliche table Ergebnis der Datenquellenfunktion zurück.

Die einfachste Implementierung ähnelt dem folgenden Beispiel:

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

Aktualisieren Sie die TripPinNavTable-Funktion, um TripPin.SuperSimpleView statt GetEntity aufzurufen.

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

Wenn Sie die Komponententests erneut ausführen, wird das Verhalten Ihrer Funktion nicht geändert. In diesem Fall leitet Ihre Table.View-Implementierung den Aufruf einfach an GetEntity weiter. Da Sie noch keine Transformationshandler implementiert haben, bleibt der ursprüngliche url Parameter unverändert.

Erste Implementierung von Table.View

Die vorherige Implementierung von Table.View ist einfach, aber nicht sehr nützlich. Die folgende Implementierung wird als Basisplan verwendet– sie implementiert keine Faltfunktionen, verfügt jedoch über das Gerüst, das Sie dafür benötigen.

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

Wenn Sie sich den Aufruf von Table.View ansehen, gibt es eine zusätzliche Wrapper-Funktion, die den handlers DatensatzDiagnostics.WrapHandlers umgibt. Diese Hilfsfunktion befindet sich im Diagnosemodul (eingeführt in der hinzufügenden Diagnosestunde ) und bietet Ihnen eine nützliche Möglichkeit, alle von einzelnen Handlern ausgelösten Fehler automatisch nachzuverfolgen.

Die GetType Funktionen und GetRows Funktionen werden aktualisiert, um zwei neue Hilfsfunktionen zu nutzen –CalculateSchema und CalculateUrl. Derzeit sind die Implementierungen dieser Funktionen ziemlich einfach. Beachten Sie, dass sie Teile davon enthalten, was zuvor von der GetEntity Funktion erledigt wurde.

Beachten Sie schließlich, dass Sie eine interne Funktion (View) definieren, die einen state Parameter akzeptiert. Wenn Sie weitere Handler implementieren, rufen sie die interne View-Funktion rekursiv auf und aktualisieren sowie übergeben state dabei weiter.

Aktualisieren Sie die TripPinNavTable Funktion erneut, indem Sie den Aufruf von TripPin.SuperSimpleView durch einen Aufruf der neuen TripPin.View Funktion ersetzen, und führen Sie die Unittests erneut aus. Es gibt noch keine neuen Funktionen, aber Sie haben jetzt einen soliden Basisplan für Tests.

Implementierung der Abfragefaltung

Da die M-Engine automatisch auf die lokale Verarbeitung umschaltet, wenn eine Abfrage nicht gefaltet werden kann, müssen Sie einige zusätzliche Schritte ausführen, um zu überprüfen, ob Ihre Table.View-Handler ordnungsgemäß funktionieren.

Die manuelle Methode, um das Verhalten beim Zusammenklappen zu validieren, besteht darin, die URL-Anfragen, die Ihre Komponententests mit einem Tool wie Fiddler machen, zu überwachen. Alternativ dazu gibt die von Ihnen hinzugefügte Diagnoseprotokollierung TripPin.Feed die vollständige URL aus, die ausgeführt wird und die die OData-Abfragezeichenfolgenparameter enthalten sollte, die Ihre Handler hinzufügen.

Eine automatisierte Möglichkeit zum Überprüfen der Abfragefaltung besteht darin, die Ausführung des Unittests fehlschlagen zu lassen, wenn eine Abfrage nicht vollständig gefaltet wird. Damit der Komponententest fehlschlägt, wenn eine Abfrage nicht vollständig gefaltet wird, öffnen Sie die Projekteigenschaften, und legen Sie "Error on Fold Failure" auf "True" fest. Wenn diese Einstellung aktiviert ist, führt jede Abfrage, die lokale Verarbeitung erfordert, zu folgendem Fehler:

We couldn't fold the expression to the source. Please try a simpler expression.

Sie können diese Änderung testen, indem Sie Ihrer Unittestdatei, die eine oder mehrere Tabellentransformationen enthält, eine neue Fact hinzufügen.

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

Hinweis

Die Einstellung "Fehler bei Faltvorgang" ist ein "alles oder nichts" Ansatz. Wenn Sie Abfragen testen möchten, die nicht als Teil der Komponententests gefaltet werden sollen, müssen Sie einige bedingte Logik hinzufügen, um Tests entsprechend zu aktivieren/deaktivieren.

Die verbleibenden Abschnitte dieses Lernprogramms fügen jeweils einen neuen Table.View-Handler hinzu. Sie nehmen einen TDD-Ansatz (Test Driven Development) vor, bei dem Sie zuerst fehlerhafte Komponententests hinzufügen und dann den M-Code implementieren, um sie zu beheben.

In den folgenden Handlerabschnitten werden die vom Handler bereitgestellten Funktionen, die OData-äquivalente Abfragesyntax, die Komponententests und die Implementierung beschrieben. Bei Verwendung des zuvor beschriebenen Gerüstcodes erfordert jede Handlerimplementierung zwei Änderungen:

  • Hinzufügen des Handlers zu Table.View , der den state Datensatz aktualisiert.
  • Ändern CalculateUrl, um die Werte aus den state abzurufen und sie der URL und/oder den Query-String-Parameter hinzuzufügen.

Umgang mit Table.FirstN mit OnTake

Der OnTake Handler empfängt einen count Parameter, bei dem es sich um die maximale Anzahl von Zeilen handelt, die aus GetRowsdem Handler entnommen werden sollen. In OData-Ausdrücken können Sie dies in den $top Abfrageparameter übersetzen.

Sie verwenden die folgenden Komponententests:

// 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)
),

Diese Tests verwenden Table.FirstN, um die Ergebnismenge auf die ersten X Zeilen zu reduzieren. Wenn Sie diese Tests mit "Error on Folding Failure auf False (der Standard) ausführen, sollten die Tests erfolgreich ausgeführt werden, aber wenn Sie Fiddler ausführen (oder die Ablaufverfolgungsprotokolle überprüfen), beachten Sie, dass die Anforderung, die Sie senden, keine OData-Abfrageparameter enthält.

Screenshot der Registerkarte

Wenn Sie Error on Folding Failure auf True festlegen, schlagen die Tests mit dem Please try a simpler expression.-Fehler fehl. Um diesen Fehler zu beheben, müssen Sie den ersten Table.View-Handler für OnTake definieren.

Der OnTake Handler sieht wie der folgende Code aus:

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

Die CalculateUrl Funktion wird aktualisiert, um den Top Wert aus dem state Datensatz zu extrahieren und den richtigen Parameter in der Abfragezeichenfolge festzulegen.

// 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

Wenn Sie die Komponententests erneut ausführen, beachten Sie, dass die URL, auf die Sie jetzt zugreifen, den $top Parameter enthält. Aufgrund der URL-Codierung erscheint $top als %24top, aber der OData-Dienst ist intelligent genug, um es automatisch zu konvertieren.

Screenshot der Registerkarte

Umgang mit Table.Skip mit OnSkip

Der OnSkip-Handler ist dem OnTake sehr ähnlich. Er empfängt einen count Parameter, bei dem es sich um die Anzahl der Zeilen handelt, die aus dem Resultset übersprungen werden sollen. Dieser Handler übersetzt sich gut auf den OData-$skip-Abfrageparameter.

Komponententests:

// 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)
),

Implementierung:

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

Übereinstimmende Updates für CalculateUrl:

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

Weitere Informationen finden Sie unter Table.Skip.

Handling Table.SelectColumns with OnSelectColumns

Der OnSelectColumns Handler wird aufgerufen, wenn der Benutzer Spalten aus dem Resultset auswählt oder entfernt. Der Handler empfängt einen list Wert, text der eine oder mehrere Spalten darstellt, die ausgewählt werden sollen.

In OData-Ausdrücken ist dieser Vorgang der $select Abfrageoption zugeordnet.

Der Vorteil der Faltspaltenauswahl wird beim Umgang mit Tabellen mit vielen Spalten deutlich. Der $select Operator entfernt nicht ausgewählte Spalten aus dem Resultset, was zu effizienteren Abfragen führt.

Komponententests:

// 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)
),

Die ersten beiden Tests wählen unterschiedliche Anzahlen von Spalten mit Table.SelectColumns aus und schließen einen Table.FirstN-Aufruf ein, um den Testfall zu vereinfachen.

Hinweis

Wenn die Tests einfach die Spaltennamen (mit Table.ColumnNames) und keine Daten zurückgeben würden, wird die Anforderung an den OData-Dienst nie tatsächlich gesendet. Dieses Verhalten tritt auf, da der Aufruf von GetType das Schema zurückgibt, das alle Informationen enthält, die die M-Engine zum Berechnen des Ergebnisses benötigt.

Der dritte Test verwendet die Option "MissingField.Ignore ", mit der das Modul M angewiesen wird, alle ausgewählten Spalten zu ignorieren, die nicht im Resultset vorhanden sind. Der OnSelectColumns Handler muss sich keine Gedanken über diese Option machen – das M-Modul behandelt sie automatisch (d. h. fehlende Spalten sind nicht in der columns Liste enthalten).

Hinweis

Die andere Option für Table.SelectColumns, MissingField.UseNull, erfordert einen Connector zum Implementieren des OnAddColumn Handlers.

Die Implementierung für OnSelectColumns macht zwei Dinge:

  • Fügt die Liste der ausgewählten Spalten zu state hinzu.
  • Berechnet den Schema Wert neu, damit Sie den richtigen Tabellentyp festlegen können.
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 wird aktualisiert, um die Liste der Spalten aus dem Zustand abzurufen und sie (mit einem Trennzeichen) für den $select Parameter zu kombinieren.

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

Verwenden von "Table.Sort" mit "OnSort"

Der OnSort Handler empfängt eine Liste von Datensätzen vom Typ:

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

Jeder Datensatz enthält ein Name Feld, das den Namen der Spalte angibt, und ein Order Feld, das " Order.Ascending " oder "Order.Descending" entspricht.

In OData-Ausdrücken ist dieser Vorgang der $orderby Abfrageoption zugeordnet. Die $orderby-Syntax hat den Spaltennamen, gefolgt von asc oder desc, um auf aufsteigende oder absteigende Reihenfolge hinzuweisen. Wenn Sie nach mehreren Spalten sortieren, werden die Werte durch ein Komma getrennt. Wenn der columns Parameter mehrere Elemente enthält, ist es wichtig, die Reihenfolge beizubehalten, in der sie angezeigt werden.

Komponententests:

// 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"})
)

Implementierung:

// 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 ]),

Aktualisierungen für CalculateUrl:

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

Behandeln von Table.RowCount mit GetRowCount

Im Gegensatz zu den anderen Abfragehandlern, die Sie implementieren, gibt der GetRowCount Handler einen einzelnen Wert zurück – die Anzahl der Zeilen, die im Resultset erwartet werden. In einer M-Abfrage wäre dieser Wert in der Regel das Ergebnis der Table.RowCount-Transformation .

Sie haben einige verschiedene Optionen zum Behandeln dieses Werts als Teil einer OData-Abfrage:

Der Nachteil des Abfrageparameteransatzes besteht darin, dass Sie die gesamte Abfrage weiterhin an den OData-Dienst senden müssen. Da die Anzahl als Teil des Resultsets inline zurückkommt, müssen Sie die erste Seite mit Daten aus dem Resultset verarbeiten. Dieser Prozess ist zwar noch effizienter als das Lesen des gesamten Resultsets und das Zählen der Zeilen, aber es ist wahrscheinlich immer noch mehr Arbeit als Sie möchten.

Der Vorteil des Pfadsegmentansatzes besteht darin, dass Sie nur einen einzelnen skalaren Wert im Ergebnis erhalten. Dieser Ansatz macht den gesamten Betrieb wesentlich effizienter. Wie in der OData-Spezifikation beschrieben, gibt das /$count Pfadsegment jedoch einen Fehler zurück, wenn Sie andere Abfrageparameter wie $top z. B. oder $skip, die ihre Nützlichkeit einschränken, einschließen.

In diesem Lernprogramm haben Sie den GetRowCount Handler mithilfe des Pfadsegmentansatzes implementiert. Um die Fehler zu vermeiden, die Sie erhalten, wenn andere Abfrageparameter enthalten sind, haben Sie auf andere Zustandswerte überprüft und einen "nicht implementierten Fehler" (...) zurückgegeben, wenn Sie einen gefunden haben. Durch das Zurückgeben eines Fehlers von einem Table.View-Handler wird der M-Engine mitgeteilt, dass der Vorgang nicht gefaltet werden kann, und es sollte stattdessen auf den Standard-Handler zurückgegriffen werden (was in diesem Fall das Zählen der Gesamtzahl der Zeilen wäre).

Fügen Sie zunächst einen Komponententest hinzu:

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

Da das /$count Pfadsegment einen einzelnen Wert (im Nur-/Text-Format) anstelle eines JSON-Resultsets zurückgibt, müssen Sie auch eine neue interne Funktion (TripPin.Scalar) hinzufügen, um die Anforderung zu erstellen und das Ergebnis zu verarbeiten.

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

Die Implementierung verwendet dann diese Funktion (wenn keine anderen Abfrageparameter im state gefunden werden):

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,

Die CalculateUrl-Funktion wird aktualisiert, um /$count an die URL anzuhängen, wenn das RowCountOnly-Feld im state gesetzt ist.

// 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,

Der neue Table.RowCount Komponententest sollte nun bestehen.

Um die Ausweichlösung zu testen, fügen Sie einen weiteren Test hinzu, der den Fehler erzwingt.

Fügen Sie zunächst eine Hilfsmethode hinzu, die das Ergebnis eines Vorgangs auf einen try Faltfehler überprüft.

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

Fügen Sie dann einen Test hinzu, der sowohl Table.RowCount als auch Table.FirstN verwendet, um den Fehler zu erzwingen.

// 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)))),

Ein wichtiger Hinweis ist hier, dass dieser Test jetzt einen Fehler zurückgibt, wenn "Fehler bei Faltungsfehler" auf false gesetzt ist, da der Table.RowCount Vorgang auf den lokalen (Standard)-Handler zurückfällt. Das Ausführen der Tests mit Fehler bei Faltung, solange true einen Fehler verursacht, ermöglicht dem Test zu bestehen Table.RowCount.

Conclusion

Durch die Implementierung von Table.View für Ihren Connector wird ihrem Code eine erhebliche Komplexität hinzugefügt. Da das M-Modul alle Transformationen lokal verarbeiten kann, ermöglicht das Hinzufügen von Table.View-Handlern keine neuen Szenarien für Ihre Benutzer, sondern führt zu einer effizienteren Verarbeitung (und potenziell glücklicheren Benutzern). Einer der Hauptvorteile der optionalen Table.View-Handler besteht darin, dass Sie inkrementell neue Funktionen hinzufügen können, ohne die Abwärtskompatibilität für den Connector zu beeinträchtigen.

Für die meisten Connectors ist OnTake ein wichtiger (und einfacher) zu implementierender Handler, der in OData $top entspricht, da er die Anzahl der zurückgegebenen Zeilen begrenzt. Die Power Query-Benutzeroberfläche führt beim Anzeigen von Vorschauen im Navigator- und Abfrage-Editor immer eine OnTake Reihe von 1000 Zeilen aus, sodass Ihre Benutzer bei der Arbeit mit größeren Datensätzen erhebliche Leistungsverbesserungen sehen können.