Skip to contents

A semantic model is the report-ready data behind Power BI and Fabric reports. It combines tables with relationships, measures, and business calculations. From R, you can query that model with DAX and refresh it after its source data changes.

Start by querying a small result. Refresh and monitoring come next, followed by the more specialized controls used for production models.

This guide uses a discovered FabricSemanticModel R6 object. Read its service fields directly. $dax_query() corresponds to fabric_pbi_dax_query(), while the $refresh*() methods correspond to the fabric_pbi_refresh*() function family:

library(fabricQueryR)

model <- fabric_semantic_models("Analytics workspace")[[1]]

Query the model with DAX

DAX is the query and calculation language used by semantic models. Use $dax_query() (fabric_pbi_dax_query()) to run a query. It normally begins with EVALUATE and returns a table. If you are new to DAX, create and test a query in the model’s DAX query view in Fabric, then use the same text from R:

rows <- model$dax_query(
  dax = "EVALUATE TOPN(100, 'Customers')"
)

head(rows)

Query the semantic model when you want its measures and relationships, or need the same business definition used by a report. Query the Warehouse or Lakehouse instead when you need the underlying source rows and SQL is a better fit.

For example, call $dax_query() (fabric_pbi_dax_query()) to group a DAX measure by a model column:

sales <- model$dax_query(
  dax = paste(
    "EVALUATE",
    "SUMMARIZECOLUMNS(",
    "  'Region'[Region],",
    "  \"Total Sales\", [Total Sales]",
    ")"
  )
)

The normal JSON mode returns one tibble and is the simplest option. The later api = "arrow" mode is useful for richer types, multiple result tables, or a stream, but requires a capacity-backed model and additional tenant settings. The timeout argument bounds the client HTTP request. For Arrow queries, arrow_options$queryTimeout separately controls the server-side query limit.

Check access before refreshing

Querying normally requires Read and Build permission on the model and the tenant setting that enables Execute Queries. Refreshing requires Write access. If a query succeeds but a refresh does not, ask the model owner or Fabric administrator to check those permissions and tenant settings.

Refresh after an upstream update

A standard refresh starts with $refresh() (fabric_pbi_refresh()) and waits with $refresh_wait() (fabric_pbi_refresh_wait()):

refresh <- model$refresh()
completed <- model$refresh_wait(refresh, timeout = 1800)

completed$state
completed$start_time
completed$end_time

Pass the returned handle to status, wait, or cancel functions without copying IDs. If you save it and restore it in another R process, authenticate again.

For example, a data-load workflow can use the Lakehouse $write_table() method (fabric_lakehouse_write_table()) and refresh only after the source update has committed successfully:

load <- lakehouse$write_table(
  table = "Sales",
  data = new_sales,
  schema = "dbo",
  mode = "overwrite"
)

refresh <- model$refresh(mode = "enhanced")
completed <- model$refresh_wait(
  refresh,
  timeout = 1800,
  cancel_on_timeout = TRUE
)

Client-side timeout in $refresh_wait() (fabric_pbi_refresh_wait()) only bounds how long R waits. Without cancel_on_timeout = TRUE, the Power BI refresh keeps running.

Use enhanced refresh controls

Enhanced refresh through $refresh() (fabric_pbi_refresh()) requires Power BI Premium, Premium per user, Embedded, or Fabric capacity. Supplying an enhanced option selects enhanced mode automatically, or set mode = "enhanced" explicitly:

refresh <- model$refresh(
  mode = "enhanced",
  type = "Full",
  objects = list(
    list(table = "Sales", partition = "2026"),
    list(table = "Calendar")
  ),
  commit_mode = "Transactional",
  max_parallelism = 4L,
  retry_count = 1L,
  timeout = "02:00:00"
)

completed <- model$refresh_wait(refresh, timeout = 5 * 60 * 60)

The submission timeout controls an individual Power BI attempt; the numeric timeout in $refresh_wait() (fabric_pbi_refresh_wait()) controls how long R waits. Use "Transactional" when the previous model should remain available unless the complete refresh succeeds. See ?fabric_pbi_refresh before using partial-batch commits or refresh-policy options.

Diagnose attempts and failures

Use $refresh_status() (fabric_pbi_refresh_status()) when a refresh takes longer than expected or fails:

status <- model$refresh_status(refresh)

status$state
status$attempts
status$messages
status$service_error
status$objects
status$details_url

The normalized state distinguishes work that is queued, running, completed, completed with warnings, failed, timed out, cancelled, or disabled. Attempt details and details_url help diagnose the service response; the complete response remains available in raw.

By default, wait raises typed R conditions for service failures, cancellation, service timeouts, and disabled refreshes. Keep the terminal detail instead when building a monitoring table by calling $refresh_wait() (fabric_pbi_refresh_wait()) with error_on_failure = FALSE:

result <- model$refresh_wait(
  refresh,
  error_on_failure = FALSE
)

if (result$state != "Completed") result$details_url

Inspect history

Inspect recent refreshes with $refresh_history() (fabric_pbi_refresh_history()) when you need to compare duration or failure patterns:

history <- model$refresh_history(top = 10L)

history[[1]]$refresh_type
history[[1]]$state
history[[1]]$attempts

# Refresh an old history entry from its request ID and stored model context
latest <- model$refresh_status(history[[1]])

Here $refresh_status() calls fabric_pbi_refresh_status(), just as it does for a newly submitted refresh.

More information

See Microsoft’s documentation for more information: - Refresh Dataset API - Enhanced refresh - Refresh execution details - Data refresh and capacity limits - How Direct Lake works