Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
In questa esercitazione, si usa PowerShell per inviare una singola richiesta API REST Execute DAX Queries che contiene più istruzioni EVALUATE, e quindi analizzare la risposta Apache Arrow con più set di risultati. Questo modello consente di recuperare diversi set di risultati correlati in un round trip da uno script di automazione di PowerShell.
Perché inviare più istruzioni EVALUATE in una sola richiesta
L'API Execute DAX Queries accetta un'unica stringa query che può contenere più istruzioni EVALUATE. Ogni istruzione restituisce il proprio insieme di risultati, e il corpo della risposta è la concatenazione di un flusso Arrow IPC per ogni istruzione EVALUATE in ordine di dichiarazione. L'invio di query correlate consente di evitare il sovraccarico per richiesta di chiamate HTTP separate, inclusa la convalida aggiuntiva dei token Microsoft Entra e l'inizializzazione del motore DAX. L'invio di più EVALUATE istruzioni in un'unica richiesta può anche contribuire ad attenuare l'impatto della limitazione del numero di richieste. Power BI limita i chiamanti a 120 richieste di query al minuto per utente per le operazioni di query del modello semantico.
Cosa costruisci
In uno script di PowerShell è possibile:
- Acquisire un token di accesso Microsoft Entra.
- Creare il corpo di una richiesta il cui
querycontiene tre istruzioniEVALUATE. - Inviare la richiesta e acquisire il flusso di risposta grezzo di Arrow IPC.
- Analizzare la risposta in un set di risultati per ogni istruzione
EVALUATE. - Visualizzare ogni set di risultati come oggetti di PowerShell.
Prerequisiti
- PowerShell 7.4 o versione successiva. Windows PowerShell 5.1 non è supportato perché il
Apache.Arrowpacchetto usato in questa esercitazione è in conflitto con l'System.Memoryassembly incluso in PowerShell 5.1. - Un'area di lavoro Power BI in capacità Premium o Fabric con almeno un modello semantico.
- Autorizzazioni di compilazione e lettura per il modello semantico.
- Modulo MicrosoftPowerBIMgmt per l'autenticazione. I cmdlet usano l'app client Power BI proprietaria di Microsoft, quindi non è necessario registrare una propria app in Microsoft Entra.
- Le librerie Apache.Arrow e Apache.Arrow.Compression .NET per deserializzare la risposta. L'API REST Execute DAX Queries comprime i buffer Arrow con la compressione frame LZ4, quindi sono necessari
Apache.Arrow.Compressione le relative dipendenze (K4os.Compression.LZ4,K4os.Compression.LZ4.Streams,K4os.Hash.xxHash,ZstdSharp.Port). Il passaggio successivo illustra come scaricarli. - Le impostazioni del tenant seguenti sono abilitate nel portale di amministrazione di Power BI:
- API REST per l'esecuzione di query del set di dati (in Impostazioni per sviluppatori).
- Consenti endpoint XMLA e Analizza in Excel con modelli semantici locali (nelle Impostazioni di integrazione).
Installare PowerShell 7.4 o versione successiva usando winget:
winget install --id Microsoft.PowerShell --source winget
Dopo l'installazione, avviare la nuova shell con pwsh. Esegui i restanti comandi in questa esercitazione in quella sessione.
Installare il modulo MicrosoftPowerBIMgmt. Il flag -Force accetta la richiesta di conferma del repository non attendibile di PowerShell Gallery.
Install-Module -Name MicrosoftPowerBIMgmt -Scope CurrentUser -Force
Scarica i pacchetti NuGet necessari ed estrai gli assembly in C:\Tools\Apache.Arrow\. Un .nupkg file è un archivio ZIP, quindi Expand-Archive funziona direttamente su di esso. Il ciclo seleziona la cartella di destinazione netX.0 di livello più alto in ogni pacchetto, in modo che gli assembly rimangano compatibili man mano che i pacchetti pubblicano target più recenti.
$dest = "C:\Tools\Apache.Arrow"
New-Item -ItemType Directory -Force -Path $dest | Out-Null
$packages = @(
"Apache.Arrow",
"Apache.Arrow.Compression",
"K4os.Compression.LZ4",
"K4os.Compression.LZ4.Streams",
"K4os.Hash.xxHash",
"ZstdSharp.Port"
)
foreach ($pkg in $packages) {
$nupkg = Join-Path $env:TEMP "$pkg.nupkg"
$expand = Join-Path $env:TEMP $pkg
if (Test-Path $expand) { Remove-Item $expand -Recurse -Force }
Invoke-WebRequest -Uri "https://www.nuget.org/api/v2/package/$pkg" -OutFile $nupkg
Expand-Archive -Path $nupkg -DestinationPath $expand -Force
$libDirs = Get-ChildItem (Join-Path $expand "lib") -Directory
$best = $libDirs | Where-Object { $_.Name -match "^net\d" } |
Sort-Object Name -Descending | Select-Object -First 1
if (-not $best) {
$best = $libDirs | Sort-Object Name -Descending | Select-Object -First 1
}
Get-ChildItem (Join-Path $best.FullName "*.dll") |
Copy-Item -Destination $dest -Force
}
1 - Autenticare
Accedere al servizio Power BI in modo interattivo, quindi estrarre un token di accesso. Il Connect-PowerBIServiceAccount cmdlet non richiede di registrare la propria app in Microsoft Entra.
Connect-PowerBIServiceAccount -WarningAction SilentlyContinue
$accessToken = (Get-PowerBIAccessToken).Authorization -replace '^Bearer\s+',''
2 - Crea una richiesta con più istruzioni EVALUATE
Definire le destinazioni dell'area di lavoro e del modello semantico. Quindi, creare il corpo della richiesta. La query proprietà è una singola stringa che contiene tre EVALUATE istruzioni separate da righe vuote.
$groupId = "YOUR_WORKSPACE_ID"
$datasetId = "YOUR_DATASET_ID"
$query = @"
EVALUATE
ROW("RowCount", COUNTROWS('Sales'))
EVALUATE
TOPN(10, 'Sales', 'Sales'[Amount], DESC)
EVALUATE
SUMMARIZECOLUMNS(
'Date'[Year],
"TotalSales", SUM('Sales'[Amount]))
"@
$body = @{
query = $query
resultsetRowcountLimit = 500000
} | ConvertTo-Json
3 - Inviare la richiesta e acquisire il flusso di risposta non elaborato
Inviare la richiesta POST e leggere il corpo della risposta come flusso binario. Usare HttpWebRequest anziché Invoke-RestMethod, Invoke-PowerBIRestMethodo Invoke-WebRequest. La risposta è un flusso IPC Arrow binario. I cmdlet di PowerShell di livello superiore interpretano i corpi di risposta come testo, che danneggia il contenuto binario.
HttpWebRequest restituisce il flusso originale senza modificarlo.
$url = "https://api.powerbi.com/v1.0/myorg/groups/$groupId" +
"/datasets/$datasetId/executeDaxQueries"
$request = [System.Net.HttpWebRequest]::Create($url)
$request.Method = "POST"
$request.ContentType = "application/json"
$request.Accept = "application/vnd.apache.arrow.stream"
$request.Timeout = 180000 # milliseconds
$request.Headers.Add("Authorization", "Bearer $accessToken")
$bodyBytes = [System.Text.Encoding]::UTF8.GetBytes($body)
$requestStream = $request.GetRequestStream()
$requestStream.Write($bodyBytes, 0, $bodyBytes.Length)
$requestStream.Close()
$response = $request.GetResponse()
$responseStream = $response.GetResponseStream()
# Buffer the response into memory so the parser can iterate over multiple Arrow IPC streams.
$memoryStream = New-Object System.IO.MemoryStream
$responseStream.CopyTo($memoryStream)
$responseStream.Close()
$response.Close()
$memoryStream.Position = 0
4 - Analizzare la risposta con più set di risultati
Il corpo della risposta è la concatenazione di un flusso IPC di Apache Arrow per ogni istruzione EVALUATE. PowerShell non viene fornito con un parser Arrow, quindi questo passaggio carica la Apache.Arrow libreria .NET tramite un helper C# inline piccolo aggiunto con Add-Type. Mantenere in C# la logica di iterazione del flusso mantiene breve il punto di chiamata e restituisce un elenco di set di risultati che lo script PowerShell può scorrere. La funzione di supporto apre un nuovo ArrowStreamReader dopo ogni marcatore di fine flusso, quindi lo stesso ciclo gestisce qualsiasi numero di insiemi di risultati nella risposta.
Add-Type -Path "C:\Tools\Apache.Arrow\Apache.Arrow.dll"
Add-Type -Path "C:\Tools\Apache.Arrow\Apache.Arrow.Compression.dll"
# Reference the full .NET reference set that ships with PowerShell 7 so the
# inline C# below can resolve BCL types such as List<T> and Dictionary<,>.
$refs = Get-ChildItem "$PSHOME\ref\*.dll" | ForEach-Object FullName
$refs += Get-ChildItem "C:\Tools\Apache.Arrow\*.dll" | ForEach-Object FullName
Add-Type -ReferencedAssemblies $refs -IgnoreWarnings -WarningAction SilentlyContinue -TypeDefinition @"
using System;
using System.Collections.Generic;
using System.IO;
using Apache.Arrow;
using Apache.Arrow.Compression;
using Apache.Arrow.Ipc;
public class DaxResultSet
{
public List<string> ColumnNames = new List<string>();
public List<Dictionary<string, object>> Rows =
new List<Dictionary<string, object>>();
}
public static class DaxMultiResultReader
{
public static List<DaxResultSet> ReadAll(Stream stream)
{
var results = new List<DaxResultSet>();
var codecFactory = new CompressionCodecFactory();
while (stream.Position < stream.Length)
{
var rs = new DaxResultSet();
bool gotSchema = false;
using (var reader = new ArrowStreamReader(stream, codecFactory, leaveOpen: true))
{
RecordBatch batch;
while ((batch = reader.ReadNextRecordBatch()) != null)
{
using (batch)
{
if (!gotSchema)
{
foreach (var f in batch.Schema.FieldsList)
rs.ColumnNames.Add(f.Name);
gotSchema = true;
}
for (int r = 0; r < batch.Length; r++)
{
var row = new Dictionary<string, object>();
for (int c = 0; c < batch.ColumnCount; c++)
row[rs.ColumnNames[c]] = GetValue(batch.Column(c), r);
rs.Rows.Add(row);
}
}
}
}
if (gotSchema) results.Add(rs);
}
return results;
}
private static object GetValue(IArrowArray a, int i)
{
if (a == null) return null;
if (a is DictionaryArray da)
{
// Resolve the dictionary index, then look up the value in the dictionary.
int dictIndex;
switch (da.Indices)
{
case Int32Array idx32: if (idx32.IsNull(i)) return null; dictIndex = idx32.GetValue(i).Value; break;
case Int16Array idx16: if (idx16.IsNull(i)) return null; dictIndex = idx16.GetValue(i).Value; break;
case Int8Array idx8: if (idx8.IsNull(i)) return null; dictIndex = idx8.GetValue(i).Value; break;
case Int64Array idx64: if (idx64.IsNull(i)) return null; dictIndex = (int)idx64.GetValue(i).Value; break;
default: return da.Indices.ToString();
}
return GetValue(da.Dictionary, dictIndex);
}
if (a is StringArray sa) return sa.GetString(i);
if (a is BooleanArray ba) return ba.IsNull(i) ? (object)null : ba.GetValue(i);
if (a is Int64Array i64) return i64.IsNull(i) ? (object)null : i64.GetValue(i);
if (a is Int32Array i32) return i32.IsNull(i) ? (object)null : i32.GetValue(i);
if (a is DoubleArray d) return d.IsNull(i) ? (object)null : d.GetValue(i);
if (a is Decimal128Array dec) return dec.GetValue(i);
if (a is Date32Array d32) return d32.GetDateTime(i);
if (a is Date64Array d64) return d64.GetDateTime(i);
if (a is TimestampArray ts) return ts.GetTimestamp(i);
return a.ToString();
}
}
"@
$results = [DaxMultiResultReader]::ReadAll($memoryStream)
Write-Host "Received $($results.Count) result sets."
5 - Lavorare con ogni set di risultati
Convertire ogni set di risultati in PSCustomObject righe. Ora puoi passare tramite pipe le righe a Where-Object, Group-Object, Export-Csv o qualsiasi altro cmdlet di PowerShell.
function ConvertTo-PSObjectRows {
param([Parameter(Mandatory)] $ResultSet)
foreach ($row in $ResultSet.Rows) {
$obj = [ordered]@{}
foreach ($col in $ResultSet.ColumnNames) { $obj[$col] = $row[$col] }
[PSCustomObject]$obj
}
}
$rowCount = ConvertTo-PSObjectRows -ResultSet $results[0]
$topProducts = ConvertTo-PSObjectRows -ResultSet $results[1]
$yearTotals = ConvertTo-PSObjectRows -ResultSet $results[2]
$rowCount | Format-Table
$topProducts | Format-Table
$yearTotals | Format-Table
Ogni variabile contiene le righe dell'istruzione corrispondente EVALUATE , nell'ordine in cui le istruzioni vengono visualizzate nella richiesta.
Troubleshooting
-
401 Non autorizzato : il token memorizzato nella cache è scaduto. Eseguire
Connect-PowerBIServiceAccountdi nuovo per aggiornarlo, quindi ripetere la lettura$accessTokendaGet-PowerBIAccessToken. -
Avvisi MSAL durante
Connect-PowerBIServiceAccount—MicrosoftPowerBIMgmtraggruppa un MSAL.NET meno recente che genera messaggi di traccia interni (ad esempio,SetAuthorityUri,TryNormalizeRealm,MsaDeviceOperationProvider is not available) con gravità di avviso. Si possono ignorare tranquillamente, purché il cmdlet stampi il bloccoEnvironment/TenantId/UserName. Per sopprimerli, specifica-WarningAction SilentlyContinue. -
HTTP 200 con un set di risultati di errore : la richiesta HTTP è riuscita, ma il flusso Arrow genera un errore. Esaminare i metadati dello schema per
IsError=truee leggereFaultCodeeFaultString. Per ulteriori informazioni, vedi Procedure consigliate per Execute DAX Queries REST API. -
Invoke-RestMethodrestituisce testo non crittografato : non usareInvoke-RestMethod,Invoke-PowerBIRestMethodoInvoke-WebRequestcon questa API. La risposta è binaria; usareHttpWebRequestcome illustrato nel passaggio 3. -
Add-Typenon riesce a essere caricatoApache.Arrow.dll: in Windows PowerShell 5.1 ilApache.Arrowpacchetto è in conflitto con l'assembly in-boxSystem.Memory. Usare PowerShell 7.4 o versione successiva. -
Nessun set di risultati restituito o un numero di set di risultati inferiore rispetto alle
EVALUATEistruzioni — Verificare che ogniEVALUATEistruzione sia sintatticamente valida di per sé. Un singoloEVALUATEnon valido fa restituire all'API un errore anziché una risposta parziale con più set di risultati.
Contenuti correlati
- Informazioni sull'API per l'esecuzione di query DAX
- Inizia con l'API REST Esegui Query DAX
- Tutorial: Creare un servizio .NET di livello intermedio con l'API REST per eseguire query DAX
- Tutorial: estrazione Python su larga scala nei notebook di Fabric
- Procedure consigliate per l'API REST Esegui query DAX
- Informazioni di riferimento sulle API REST Per l'esecuzione di query DAX