In this post, I’ll show you how to build a fully automated, config-driven pipeline that ingests tables from an on-prem SQL Server instance (or Docker container) into Microsoft Fabric OneLake, leveraging:
- DLT (Data Load Tool) for extraction and Delta file generation
- Delta format for efficient incremental loads
- Microsoft Fabric REST API for programmatic shortcut creation
- No Fabric capacity used — data lands in
/Filesand becomes queryable via/Tables
The beauty? It refreshes with a single script, and avoids manual setup of tables.
Tools and Architecture
- Source: SQL Server (on-prem or Docker)
- Destination: Microsoft Fabric OneLake
- Ingestion Tool: dlt (Data Load Tool)
- Shortcut Interface: Microsoft Fabric REST API
- File Format: Delta Lake (Parquet + transaction log)
- Package Manager: uv
Why This Architecture?
Fabric shortcuts let you expose data in /Files as queryable /Tables without copying or moving it. Combined with DLT’s filesystem destination and Delta Lake support, you get:
- Incremental loads that only process changed rows
- Delta transaction logs that Fabric can read natively
- Shortcut registration that only needs to happen once per table
What This Pipeline Does
- Ingests 3 tables (
Products,Customers,Orders) from SQL Server using dlt - Writes Delta format files to
OneLake/Files/mssql_tables_dataset/... - Automatically creates shortcuts via Fabric REST API at
OneLake/Tables/... - Incremental loads based on a
last_updatedcolumn - Avoids re-registering tables on every run
Core Pipeline Code
The dlt pipeline uses the sql_database source with write_disposition="merge" and dlt.sources.incremental to track the last_updated column:
import dlt
from dlt.sources.sql_database import sql_database
TABLES = ["Products", "Customers", "Orders"]
@dlt.source
def mssql_source(
connection_string: str = dlt.secrets.value,
):
return [
sql_database(
connection_string,
schema="dbo",
table_names=[t],
).with_resources(t)
for t in TABLES
]
def build_pipeline():
return dlt.pipeline(
pipeline_name="mssql_to_onelake",
destination="filesystem",
dataset_name="mssql_tables_dataset",
)
if __name__ == "__main__":
pipeline = build_pipeline()
source = mssql_source()
for resource in source.resources.values():
resource.apply_hints(
write_disposition="merge",
incremental=dlt.sources.incremental("last_updated"),
)
load_info = pipeline.run(source)
print(load_info)
This handles incremental syncs using the last_updated column. The result is stored in Delta format under /Files.
Creating Shortcuts Automatically
After the Delta files are generated, we use the Microsoft Fabric REST API to create table shortcuts dynamically. The function checks whether a shortcut already exists before attempting to create it:
import requests
def get_access_token(tenant_id: str, client_id: str, client_secret: str) -> str:
url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
resp = requests.post(url, data={
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
"scope": "https://api.fabric.microsoft.com/.default",
})
resp.raise_for_status()
return resp.json()["access_token"]
def shortcut_exists(
token: str,
workspace_id: str,
lakehouse_id: str,
table_name: str,
) -> bool:
url = (
f"https://api.fabric.microsoft.com/v1/workspaces/{workspace_id}"
f"/items/{lakehouse_id}/shortcuts/Tables/{table_name}"
)
resp = requests.get(url, headers={"Authorization": f"Bearer {token}"})
return resp.status_code == 200
def create_shortcut(
token: str,
workspace_id: str,
lakehouse_id: str,
onelake_path: str,
table_name: str,
) -> None:
if shortcut_exists(token, workspace_id, lakehouse_id, table_name):
print(f"Shortcut already exists for {table_name}, skipping.")
return
url = (
f"https://api.fabric.microsoft.com/v1/workspaces/{workspace_id}"
f"/items/{lakehouse_id}/shortcuts"
)
payload = {
"path": "Tables",
"name": table_name,
"target": {
"type": "OneLake",
"oneLake": {
"workspaceId": workspace_id,
"itemId": lakehouse_id,
"path": f"Files/{onelake_path}/{table_name}",
},
},
}
resp = requests.post(
url,
json=payload,
headers={"Authorization": f"Bearer {token}"},
)
resp.raise_for_status()
print(f"Shortcut created for {table_name}")
You only need to run this once per table — subsequent pipeline runs just update the Delta log, and the shortcut automatically reflects the latest data.
Environment Configuration
OneLake supports the same APIs as Azure Data Lake Storage (ADLS) Gen2, so dlt’s filesystem destination can target it directly. Configure your secrets.toml:
[sources.sql_database]
connection_string = "mssql+pyodbc://user:pass@host/dbname?driver=ODBC+Driver+17+for+SQL+Server"
[destination.filesystem]
bucket_url = "abfss://<workspace-name>@onelake.dfs.fabric.microsoft.com/<lakehouse-name>.Lakehouse/Files"
[destination.filesystem.credentials]
azure_client_id = "..."
azure_client_secret = "..."
azure_tenant_id = "..."
[fabric]
workspace_id = "..."
lakehouse_id = "..."
tenant_id = "..."
client_id = "..."
client_secret = "..."
Incremental Loading in Action
When you insert new rows or update the last_updated column in your SQL Server tables, all you need to do is rerun the pipeline script.
Behind the scenes:
- dlt uses the
last_updatedcolumn (configured viadlt.sources.incremental) to detect only new or changed rows - These changes are written as new Parquet files, and the Delta Lake transaction log (
_delta_log) is updated to reflect the new state - Since Fabric shortcuts point directly to the
/Files/mssql_tables_dataset/{table}folders, they automatically reflect the latest data — without any need to refresh or reload
There’s no need to manually re-register tables, no data duplication, and no reprocessing of unchanged rows. The shortcut acts as a real-time lens into your latest data.
Caveats and Gotchas
Admin Consent Required:
The Fabric REST API requires Power BI service-level permissions such as Tenant.Read.All and Tenant.ReadWrite.All. These must be granted via admin consent in Entra ID — worth looping in your tenant admin early.
One Table, One Folder: Each Delta table must be stored in a dedicated folder within OneLake. Avoid merging multiple tables into a single folder to ensure proper shortcut creation and queryability.
Stable Schemas Are Critical: Delta tables must remain schema-consistent over time. If your source schema changes, implement schema evolution strategies to prevent load failures or shortcut inconsistencies.
Final Thoughts
This setup helped me:
- Reduce costs — dlt writes Delta files directly to OneLake (storage cost only, no compute)
- Simplify ingestion from legacy SQL Server systems
- Avoid manual table creation or refresh in Fabric
- Automate shortcut management via API
- Keep capacity usage low — shortcut creation consumes no Fabric Compute Units
If you’re working with SQL Server and want a lightweight path into Fabric without standing up Spark clusters or Fabric Data Factory pipelines, this setup is worth exploring.