Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este tutorial, usará PowerShell para enviar una única solicitud a la API REST Execute DAX Queries que contiene varias EVALUATE sentencias y, a continuación, analizar la respuesta Apache Arrow de múltiples conjuntos de resultados. Este patrón le permite recuperar varios conjuntos de resultados relacionados en un recorrido de ida y vuelta desde un script de automatización de PowerShell.
¿Por qué enviar varias sentencias EVALUATE en una sola solicitud?
La API Execute DAX Queries acepta una sola query cadena que puede contener varias EVALUATE instrucciones. Cada sentencia devuelve su propio conjunto de resultados, y el cuerpo de la respuesta es la concatenación de un flujo Arrow IPC por cada sentencia EVALUATE, según el orden de declaración. Enviar consultas relacionadas de forma conjunta evita la sobrecarga asociada a cada solicitud de realizar llamadas HTTP separadas, incluida la validación adicional de tokens de Microsoft Entra y la inicialización del motor DAX. Enviar varias EVALUATE sentencias en una sola solicitud también puede ayudar a mitigar el impacto de la limitación del número de solicitudes. Power BI limita las llamadas a 120 solicitudes de consulta por minuto por usuario para las operaciones de consulta de modelo semántico.
Lo que construyes
En un script de PowerShell, puede:
- Adquiera un token de acceso de Microsoft Entra.
- Cree un cuerpo de solicitud cuyo
querycontenga tres sentenciasEVALUATE. - Envíe la solicitud y capture el flujo en bruto de respuesta IPC de Arrow.
- Analice la respuesta en un conjunto de resultados por cada instrucción
EVALUATE. - Muestra cada conjunto de resultados como objetos de PowerShell.
Prerequisites
- PowerShell 7.4 o posterior. Windows PowerShell 5.1 no se admite porque el paquete
Apache.Arrowusado en este tutorial entra en conflicto con el ensambladoSystem.Memoryincluido en PowerShell 5.1. - Un área de trabajo de Power BI en la capacidad Premium o Fabric con al menos un modelo semántico.
- Permisos de compilación y lectura en el modelo semántico.
- El módulo MicrosoftPowerBIMgmt para la autenticación. Los cmdlets usan la aplicación cliente de Power BI propia de Microsoft, por lo que no necesita registrar una aplicación propia en Microsoft Entra.
- Las bibliotecas Apache.Arrow y Apache.Arrow.Compression .NET para deserializar la respuesta. La API REST de Execute DAX Queries comprime los búferes de Arrow con compresión de tramas LZ4, por lo que
Apache.Arrow.Compressiony sus dependencias (K4os.Compression.LZ4,K4os.Compression.LZ4.Streams,K4os.Hash.xxHash,ZstdSharp.Port) son necesarios. En el paso siguiente se muestra cómo descargarlos. - Las siguientes opciones de configuración del inquilino habilitadas en el portal de administración de Power BI:
- API REST para ejecutar consultas de conjuntos de datos (en Configuración del desarrollador).
- Permitir endpoints XMLA y Analizar en Excel con modelos semánticos locales (dentro de configuración de integración).
Instale PowerShell 7.4 o posterior mediante winget:
winget install --id Microsoft.PowerShell --source winget
Después de la instalación, inicie el nuevo shell con pwsh. Ejecute los comandos restantes de este tutorial desde esa sesión.
Instale el módulo MicrosoftPowerBIMgmt. El indicador -Force acepta el aviso de repositorio no confiable de Galería de PowerShell.
Install-Module -Name MicrosoftPowerBIMgmt -Scope CurrentUser -Force
Descargue los paquetes NuGet necesarios y extraiga sus ensamblados en C:\Tools\Apache.Arrow\. Un .nupkg archivo es un archivo ZIP, por lo que Expand-Archive funciona directamente en él. El bucle selecciona la carpeta de destino más alta netX.0 de cada paquete para que los ensamblados permanezcan compatibles a medida que los paquetes publiquen destinos más recientes.
$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 - Autenticación
Inicie sesión en el servicio Power BI de forma interactiva y, a continuación, extraiga un token de acceso. El Connect-PowerBIServiceAccount cmdlet no requiere que registre su propia aplicación en Microsoft Entra.
Connect-PowerBIServiceAccount -WarningAction SilentlyContinue
$accessToken = (Get-PowerBIAccessToken).Authorization -replace '^Bearer\s+',''
2 - Crear una solicitud con varias sentencias EVALUATE
Defina el área de trabajo y los destinos del modelo semántico. A continuación, compile el cuerpo de la solicitud. La query propiedad es una sola cadena que contiene tres EVALUATE instrucciones separadas por líneas en blanco.
$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 - Enviar la solicitud y capturar el flujo de respuesta sin procesar
Envíe la solicitud POST y lea el cuerpo de la respuesta como una secuencia binaria. Use HttpWebRequest en lugar de Invoke-RestMethod, Invoke-PowerBIRestMethodo Invoke-WebRequest. La respuesta es una secuencia IPC de Arrow binaria. Los cmdlets de PowerShell de nivel superior interpretan los cuerpos de respuesta como texto, lo que daña el contenido binario.
HttpWebRequest devuelve el flujo en bruto sin modificar.
$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 - Análisis de la respuesta de varios conjuntos de resultados
El cuerpo de la respuesta es la concatenación de un flujo IPC de Apache Arrow por cada sentencia EVALUATE. PowerShell no incluye un analizador de Arrow, por lo que este paso carga la biblioteca .NET Apache.Arrow a través de una pequeña utilidad en línea en C# añadida con Add-Type. Mantener la lógica de bucle de flujo en C# mantiene el sitio de llamada corto y devuelve una lista de conjuntos de resultados que el script de PowerShell puede iterar. El asistente abre un nuevo ArrowStreamReader después de cada marcador de fin de flujo, por lo que el mismo bucle controla cualquier número de conjuntos de resultados en la respuesta.
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 - Trabajar con cada conjunto de resultados
Convierta cada conjunto de resultados en PSCustomObject filas. Ahora puede canalizar las filas mediante Where-Object, Group-Object, Export-Csv o cualquier otro cmdlet de 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
Cada variable contiene las filas de la instrucción correspondiente EVALUATE , en el orden en que las instrucciones aparecen en la solicitud.
Solución de problemas
-
401 No autorizado : el token almacenado en caché expiró. Vuelva a ejecutar
Connect-PowerBIServiceAccountpara actualizarlo y, a continuación, vuelva a leer$accessTokendesdeGet-PowerBIAccessToken. -
Advertencias de MSAL durante
Connect-PowerBIServiceAccount—MicrosoftPowerBIMgmtincluye una versión anterior de MSAL.NET que emite mensajes internos de seguimiento (por ejemplo,SetAuthorityUri,TryNormalizeRealm,MsaDeviceOperationProvider is not available) con nivel de advertencia. Se pueden omitir sin problema siempre que el cmdlet imprima el bloqueEnvironment/TenantId/UserName. Para suprimirlos, pase-WarningAction SilentlyContinue. -
HTTP 200 con un conjunto de resultados de error — La solicitud HTTP se completó correctamente, pero el flujo Arrow contiene un error. Inspeccione los metadatos del esquema para
IsError=truey leaFaultCodeyFaultString. Para más información, consulte Prácticas recomendadas para la API REST Execute DAX Queries. -
Invoke-RestMethoddevuelve texto desagrabado : no useInvoke-RestMethod,Invoke-PowerBIRestMethodoInvoke-WebRequestcon esta API. La respuesta es binaria; useHttpWebRequestcomo se muestra en el paso 3. -
Add-Typeno se cargaApache.Arrow.dll— En Windows PowerShell 5.1, el paqueteApache.Arrowentra en conflicto con el ensambladoSystem.Memoryincluido. Use PowerShell 7.4 o una versión posterior. -
Se devuelven menos conjuntos de resultados de los correspondientes a las
EVALUATEinstrucciones — Confirme que cadaEVALUATEinstrucción es sintácticamente válida por sí sola. Un únicoEVALUATEno válido hace que la API devuelva un error en lugar de una respuesta parcial con varios conjuntos de resultados.
Contenido relacionado
- Comprender la API de ejecución de consultas DAX
- Introducción a la API REST Execute DAX Queries (Ejecutar consultas DAX)
- Tutorial: cree un servicio de nivel medio .NET con la API REST Execute DAX Queries
- Tutorial: Extracción de Python de gran volumen en cuadernos de Fabric
- Mejores prácticas para la API REST para ejecutar consultas DAX
- Referencia de la API REST de ejecución de consultas DAX