Standard Operating Procedure

SQL Server → Azure SQL Database

Step-by-step procedure for migrating an on-premises SQL Server database to Azure SQL Database (General Purpose or Business Critical tier) using Endrias Bridge.

Tool: Endrias Bridge  ·  Source: SQL Server 2016+ on-prem or VM  ·  Target: Azure SQL Database

📖 Overview & Scope

This SOP covers a one-time full copy (schema + data) of a SQL Server on-premises or VM database into an existing Azure SQL Database, using the Endrias Bridge Assessment and Migration tabs. Because source and target are the same engine family (T-SQL), most types map 1:1 — see the Type Conversions table for the few exceptions (deprecated types, FILESTREAM, temporal SysEnd clamping).

ℹ️
Bidirectional support: Endrias Bridge also supports Azure SQL → SQL Server. Use the same steps with source and target swapped — useful for on-prem DR seeding or test environment refresh.
ℹ️
Estimated time: ~10–15 minutes for setup and assessment, plus the data copy duration. Azure SQL throughput depends on the service tier — General Purpose S3+ or Business Critical is recommended for migration workloads.

Prerequisites — Source (SQL Server)

RequirementNotes
SQL login with db_datareader + VIEW SERVER STATEMinimum
db_owner recommended for CDC stream modeRecommended
ODBC Driver 17 for SQL Server installed on migration hostRequired
TCP 1433 open from migration host to source serverRequired
SQL Server version 2016+ (Compatibility level 130+)Required
-- Verify source SQL Server version and compatibility level
SELECT @@VERSION;
SELECT name, compatibility_level
FROM sys.databases
WHERE name = 'EndriasBridge';

-- Grant minimum permissions on source
USE EndriasBridge;
CREATE USER MigrationUser FOR LOGIN MigrationUser;
ALTER ROLE db_datareader ADD MEMBER MigrationUser;
GRANT VIEW SERVER STATE TO MigrationUser;

Prerequisites — Target (Azure SQL Database)

Endrias Bridge does not create the Azure SQL Database — it must exist and be accessible before Step 1. Complete the following in the Azure Portal before connecting:

1. Add migration host firewall rule:

Navigate to Azure Portal → SQL Server (logical server) → Networking → Firewall rules. Add the public IP of your migration host as an allowed inbound rule.

2. Create contained database user on target:

-- Run on Azure SQL Database (EndriasBridgeTarget) via SSMS or Azure Portal Query Editor
CREATE USER MigrationUser WITH PASSWORD = 'your-password';
ALTER ROLE db_owner ADD MEMBER MigrationUser;

3. Verify tier capacity:

TierMax SizeMigration Recommendation
Basic2 GBNot recommended for large migrations
Standard S3100 GBMinimum for production migration throughput
General PurposeUp to 4 TBRecommended
Business CriticalUp to 4 TBRecommended for large, time-sensitive migrations
⚠️
Azure SQL Database drops idle pooled connections after approximately 30 minutes. Endrias Bridge detects TCP 10054 / forcibly closed errors and automatically retries once after disposing the connection pool — no manual intervention is needed for this known Azure behavior.
1

Configure Connections

Open Endrias Bridge and navigate to Step 5 — DB Migration. Fill in both connection blocks as shown below. For the Azure SQL target, use the fully qualified server hostname from the Azure Portal (yourserver.database.windows.net) and port 1433.

  Endrias Bridge — Step 5: Source & Target Configuration

SOURCE DATABASE

SQL Server
sql-source-server
(blank)
EndriasBridge
MigrationUser
••••••••••••

TARGET DATABASE

Azure SQL
sql-target.database.windows.net
1433
EndriasBridgeTarget
MigrationUser
••••••••••••
Test Connections
Save Configuration
💡
Click Test Connections first. Azure SQL connection failures at this stage are almost always firewall-related — confirm the migration host public IP is in the Azure SQL Server firewall allowlist.
2

Run Assessment

Switch to the Assessment tab and click Run Assessment. The tool connects to the source only at this stage. Since source and target are the same engine family, most objects will show as compatible — look for deprecated type warnings (NTEXT, IMAGE, TEXT) and any FILESTREAM columns.

  Endrias Bridge
Assessment
Migration
Jobs
Schema Objects
Tools
Database Assessment
Discovers source database objects and checks compatibility with target.
Run Assessment

SOURCE OBJECTS

📁 Tables (58)
  Users   45,200 rows
  Products   8,100 rows
  Orders   2,400,000 rows
  CustomCodeDetail   1,240 rows
  … (54 more)

COMPATIBILITY ISSUES

• Reports.Content: NTEXT → NVARCHAR(MAX) (deprecated)
• Documents.FileData: IMAGE → VARBINARY(MAX) (deprecated)
• CustomCodeDetail: SysEnd temporal boundary → clamp warning
• (Most standard T-SQL types: 1:1 mapping)
• … (full list per scanned table)
ℹ️
Because source and target share the T-SQL engine, type compatibility issues are minimal. The main items to review are deprecated types (NTEXT, IMAGE, TEXT) that Endrias Bridge upgrades automatically, FILESTREAM columns (migrated as VARBINARY(MAX) — files not copied), and temporal SysEnd clamping warnings (informational).
3

Select Tables

Switch to the Migration tab. Click Load Tables from Source to populate the table list. For a SQL Server → Azure SQL migration, all tables are generally safe to include — review the list for any tables that contain FILESTREAM data or rely on cross-database joins that will not work post-migration.

  Endrias Bridge
Assessment
Migration
Jobs
Schema Objects
Tools
Migrate Schema & Data
Select tables and copy schema / data to target database.
Migrate schema (CREATE TABLE)
Migrate data (INSERT rows)
Sync mode:
Full copy
Incremental (watermark column)
SQL Server CDC
Load Tables from Source
Start Migration
Verify Data Integrity
Export Report
Users
Products
Orders
CustomCodeDetail
Reports
… (all 58 tables checked)
ℹ️
Unlike cross-engine migrations, you generally do not need to exclude tables for type-compatibility reasons. Exclude tables only if they contain FILESTREAM data (files must be handled separately via Azure Blob Storage) or if they are purely staging/temp tables not needed on target.
4

Start Migration & Monitor

Click Start Migration. Watch the Migration Log update live. The log will show type upgrade notes for deprecated types and any temporal SysEnd clamping warnings — these warnings are informational and do not indicate data loss.

  Endrias Bridge — Migration Log
14:02:11 --- Data type conversions (SQL Server -> Azure SQL) ---
14:02:11 Most types map 1:1 (same engine family)
14:02:11 EndriasBridge.Reports.Content: NTEXT -> NVARCHAR(MAX) (deprecated type)
14:02:11 WARNING: SysEnd clamped: 1240 row(s) in CustomCodeDetail had SysEnd=9999-12-31
14:02:11 -> clamped to 9998-12-31 (ODBC Driver 17 limitation)
14:02:12 Schema: created 58 table(s)
14:02:12 [1/58] Users ... 45,200 row(s)
14:02:13 [2/58] Products ... 8,100 row(s)
14:02:45 --- Full copy: 2,400,000+ row(s) in 52s ---
14:02:45 --- Done ---
ℹ️
Log lines showing WARNING: SysEnd clamped are informational — data lands correctly on target. ODBC Driver 17 cannot write the year-9999 datetime boundary, so Endrias Bridge automatically clamps SysEnd = 9999-12-31 to 9998-12-31. This is a known driver limitation, not a data integrity issue.
⚠️
If you see a TCP 10054 or forcibly closed error mid-migration, Endrias Bridge will automatically retry after disposing the connection pool. This is expected behavior for Azure SQL idle connection drops — the migration will resume and complete without manual intervention.
5

Verify & Export Report

  1. Click Verify Data Integrity — compares source vs. target row counts for every migrated table and logs any mismatches.
  2. Click Export Report (enabled once migration finishes) to save an HTML/text summary of what was created, copied, and any warnings.
  3. Spot-check key tables on the Azure SQL target using SSMS or Azure Portal Query Editor.
-- Run on Azure SQL target after migration to spot-check row counts
SELECT t.name AS TableName, p.rows AS RowCount
FROM sys.tables t
INNER JOIN sys.partitions p ON t.object_id = p.object_id
WHERE p.index_id IN (0, 1)
ORDER BY t.name;
6

Post-Migration

Azure SQL Database (PaaS) does not support all SQL Server on-prem features. Review the following items after migration completes.

1. Recreate SQL Agent jobs as Azure Elastic Jobs:

SQL Server Agent is not available on Azure SQL Database (PaaS). Export all job definitions from the source using the Schema Objects tab, then recreate them as Azure Elastic Jobs via the Azure Portal or T-SQL elastic jobs API.

2. Review FILESTREAM / FILETABLE columns:

-- Identify FILESTREAM columns on target (will be VARBINARY(MAX))
SELECT OBJECT_NAME(c.object_id) AS TableName, c.name AS ColumnName, t.name AS TypeName
FROM sys.columns c
JOIN sys.types t ON c.user_type_id = t.user_type_id
WHERE t.name = 'varbinary' AND c.max_length = -1
ORDER BY TableName, ColumnName;

FILESTREAM files were not migrated — binary column data is present in VARBINARY(MAX) but the file-system projection is gone. Migrate file payloads separately to Azure Blob Storage and update application connection strings accordingly.

3. Validate linked server references:

Linked servers are not available on Azure SQL Database. Replace with OPENDATASOURCE, elastic queries (external tables), or Azure Data Factory pipelines for any cross-database or cross-server references.

⚠️
CLR assemblies require explicit enablement on Azure SQL. The sp_configure 'clr enabled' option is not user-accessible on PaaS — submit an Azure support request if CLR is required for your workload.
⚠️
Cross-database queries using 3-part names (OtherDB.dbo.TableName) will fail on Azure SQL. Replace these with elastic queries (external data sources and external tables) or consolidate schemas into a single database.

🔄 Live Migration — Full-Load-First Sequence

⚠️
Do NOT start CDC streaming immediately when you click Start Migration. Full load and CDC streaming are two separate phases that must run in order. Starting the stream before the full load finishes means only changes from "now" forward are captured — all pre-existing rows will be missing on the target.
PhaseActionNotes
1 Enable CDC on source (one-time setup) Must be done before the full load starts. Tool records the starting LSN anchor at this point.
2 Click Start Migration → Full Load MigrationBridge saves sys.fn_cdc_get_max_lsn() before copying row 1. Source stays fully live throughout.
3 Full load completes Watch Migration Log — all tables show row counts. Do not start the stream until this step finishes.
4 Start CDC Stream Tool replays all changes that accumulated on the transaction log during the full load, from the saved LSN forward. Catches up to real-time, then enters sub-second tail mode. No changes are missed.
5 Monitor lag < 1 second Watch Replication tab or Migration Log. When lag is consistently under 1 second for 5 minutes, you are in steady-state tail mode.
ℹ️
Why the LSN anchor matters: MigrationBridge calls sys.fn_cdc_get_max_lsn() and saves that position before copying a single row. If the full load takes 90 minutes and thousands of rows change on the source during that time, the stream catch-up phase after the load will replay every one of those changes in order — nothing is lost, regardless of how long the full load took.

Live Migration — Source Pre-flight Checks

Run all four blocks on the VM source before enabling CDC. All checks must pass.

USE YourSourceDB;
GO

-- 1. SQL Agent running? (CDC capture job requires it)
SELECT servicename, status_desc
FROM   sys.dm_server_services
WHERE  servicename LIKE 'SQL Server Agent%';
-- Expected: Running

-- 2. Recovery model FULL?
SELECT name, recovery_model_desc
FROM   sys.databases WHERE name = DB_NAME();
-- Expected: FULL

-- 3. Containment? (PARTIAL blocks CDC -- see note below)
SELECT name, containment_desc
FROM   sys.databases WHERE name = DB_NAME();
-- Expected: NONE

-- 4. CDC already enabled?
SELECT name, is_cdc_enabled
FROM   sys.databases WHERE name = DB_NAME();
-- Expected: 0 (will enable next) or 1 (already done)

-- 5. Permission check
SELECT IS_MEMBER('db_owner') AS is_db_owner, SUSER_SNAME() AS login;
⚠️
Containment = PARTIAL blocks CDC. If the containment check returns PARTIAL, you must: (1) Drop every contained database user (SELECT name FROM sys.database_principals WHERE authentication_type_desc = 'DATABASE'), (2) Verify zero rows remain before continuing, (3) Run ALTER DATABASE YourSourceDB SET CONTAINMENT = NONE from master (single-user mode required), (4) Re-run sp_cdc_enable_db. The ALTER fails silently if even one contained user still exists.

📡 Round 1 — CDC Stream Setup (VM Source)

1

Enable CDC on the database

USE YourSourceDB;
GO
EXEC sys.sp_cdc_enable_db;
GO
GRANT SELECT ON SCHEMA::cdc TO [Migration];
GO

-- Set 30-day retention immediately (default is 3 days)
EXEC sys.sp_cdc_change_job @job_type='cleanup', @retention=43200;  -- 43200 min = 30 days
GO

-- Verify CDC on and jobs created
SELECT name, is_cdc_enabled FROM sys.databases WHERE name = DB_NAME();
SELECT name, enabled FROM msdb.dbo.sysjobs WHERE name LIKE 'cdc.YourSourceDB%';
-- Expected: 2 rows (capture + cleanup)
ℹ️
Azure SQL Database only — extra required step. If the source were Azure SQL, you would also need to run EXEC sys.sp_cdc_add_job @job_type = N'capture' immediately after sp_cdc_enable_db, because Azure SQL does not auto-create the capture job. On a VM source with SQL Agent running this step is automatic — skip it.

Target (Azure SQL): CDC does NOT need to be enabled on the target. MigrationBridge applies changes as plain INSERT / UPDATE / DELETE. The target only needs db_owner.
2

Enable CDC on the test table

USE YourSourceDB;
GO
EXEC sys.sp_cdc_enable_table
    @source_schema        = N'dbo',
    @source_name          = N'YourTable',
    @role_name            = NULL,
    @supports_net_changes = 1;
GO

-- Generate enable statements for ALL tables at once
SELECT
    'EXEC sys.sp_cdc_enable_table @source_schema=N''' + s.name + ''','
    + '@source_name=N''' + t.name + ''','
    + '@role_name=NULL,@supports_net_changes=1;' AS stmt
FROM   sys.tables t
JOIN   sys.schemas s        ON t.schema_id = s.schema_id
LEFT JOIN cdc.change_tables ct ON ct.source_object_id = t.object_id
WHERE  s.name NOT IN ('cdc','sys') AND ct.capture_instance IS NULL
ORDER  BY s.name, t.name;
-- Copy the output and execute the generated statements
3

Configure MigrationBridge and run

  1. Source: SQL Server → VM hostname/IP, YourSourceDB, MigrationUser
  2. Target: Azure SQL Database → yourserver.database.windows.net, port 1433
  3. Migrate tab → Sync Mode → CDC Stream (real-time, SQL Server)
  4. Select tables → click Start Migration — full load runs, LSN anchor saved
  5. Wait for full load to complete — watch Migration Log until all tables show row counts
  6. Click Start CDC Stream — catch-up begins, then enters tail mode
4

Test and validate

-- Run on VM source
USE YourSourceDB;
UPDATE dbo.YourTable SET SomeColumn = 'CDC_TEST_1' WHERE ID = 1;

-- Run on Azure SQL target within ~1 second
SELECT SomeColumn FROM dbo.YourTable WHERE ID = 1;
-- Expected: 'CDC_TEST_1'

-- Also test UPDATE and DELETE
UPDATE dbo.YourTable SET SomeColumn = 'CDC_UPDATE' WHERE ID = 1;
DELETE FROM dbo.YourTable WHERE ID = 999;   -- use a real dispensable row

🔁 Round 2 — CT Sync Setup (Change Tracking)

Change Tracking requires no SQL Agent and is simpler to configure. Use this if CDC is unavailable or as a comparison test.

1

Enable Change Tracking on the VM source

USE YourSourceDB;
GO

-- Database level — 30-day retention
ALTER DATABASE YourSourceDB
SET CHANGE_TRACKING = ON
    (CHANGE_RETENTION = 30 DAYS, AUTO_CLEANUP = ON);
GO

-- Per table
ALTER TABLE dbo.YourTable
ENABLE CHANGE_TRACKING WITH (TRACK_COLUMNS_UPDATED = ON);
GO

-- Generate statements for all tables
SELECT 'ALTER TABLE ' + s.name + '.' + t.name
      + ' ENABLE CHANGE_TRACKING WITH (TRACK_COLUMNS_UPDATED=ON);'
FROM   sys.tables t
JOIN   sys.schemas s ON t.schema_id = s.schema_id
WHERE  s.name NOT IN ('cdc','sys')
ORDER  BY s.name, t.name;

-- Verify
SELECT db_name, retention_period, retention_period_units_desc, is_auto_cleanup_on
FROM   sys.change_tracking_databases WHERE database_id = DB_ID();

SELECT t.name, ct.is_track_columns_updated_on
FROM   sys.change_tracking_tables ct
JOIN   sys.tables t ON ct.object_id = t.object_id;
2

Configure MigrationBridge and run

  1. Same Source + Target connections as Round 1
  2. Sync Mode → CT Sync (watermark)
  3. Click Start Migration → full load runs
  4. After full load → click Start CT Sync stream
3

Test all three DML operations

-- Run on VM source
UPDATE dbo.YourTable SET SomeColumn = 'CT_UPDATE' WHERE ID = 1;
DELETE FROM dbo.YourTable WHERE ID = 999;
INSERT INTO dbo.YourTable (ID, SomeColumn) VALUES (9999, 'CT_INSERT');

-- Verify each on Azure SQL target within ~1-5 seconds

⚖️ CDC Stream vs CT Sync — Comparison

FeatureCDC StreamCT Sync
Replication latency~250ms (sub-second)~1–5 seconds (poll interval)
DELETE captureYes — native from transaction logYes — tombstone pattern
SQL Agent dependencyYes — capture job requires Agent runningNo — no Agent needed
Recovery model requirementFULL requiredAny recovery model
Setup complexityMore steps (sp_cdc_enable_db + per table)Simpler (ALTER DATABASE + ALTER TABLE)
Azure SQL sourceRequires sp_cdc_add_job manuallyNative — recommended for Azure SQL sources
Best forSub-second RPO, high-churn tables, complianceSimpler setup, lower-churn DBs, Azure SQL sources
Rollback / disablesp_cdc_disable_table → sp_cdc_disable_dbALTER TABLE DISABLE CHANGE_TRACKING → ALTER DATABASE SET CHANGE_TRACKING = OFF

🖥️ UI Busy / Not Responding During Migration

ℹ️
This is expected behavior, not a crash. MigrationBridge runs migration and replication work on background threads, but certain UI update operations — particularly when the Migration Log flushes a large batch of rows or when the tool is processing a high-throughput table — can cause the Windows title bar to temporarily show "(Not Responding)". The migration continues to run correctly in the background.

What you will see

SymptomCauseAction
Title bar shows "(Not Responding)" UI thread is busy flushing log lines while a large table is being copied at high throughput Wait — do not close the window. The window will become responsive again when the batch finishes. This typically lasts 5–30 seconds on large tables.
Progress bar frozen Same as above — UI update is queued while background thread is running Wait. Check Migration Log for last completed table — if rows are incrementing in the log, migration is progressing normally.
Migration Log stops updating Log panel is waiting for the UI thread to flush Normal during large table copy. When the table finishes, the log will catch up and display all rows at once.
CDC Stream shows no new events for 10+ seconds Source has no changes, or the polling interval is idle Make a test change on the source (UPDATE one row). If it appears on the target within 1–2 seconds, the stream is healthy.
Window freezes and stays frozen > 5 minutes Possible genuine hang — distinct from normal busy state Check Task Manager → CPU/Memory for MigrationBridge process. If CPU is 0% and memory is not growing, check migration log file at C:\MigrationT\migration_logs\ for the last logged entry to diagnose.

How to tell if migration is still running

  1. Open Task Manager → find python.exe or MigrationBridge.exe → confirm CPU % is above 0 and memory is active. If so, the migration is running — the UI is just catching up.
  2. Open the latest log file directly: C:\MigrationT\migration_logs\ — the file updates in real time even when the UI is frozen.
  3. For CDC Stream: check the source with SELECT sys.fn_cdc_get_max_lsn() AS source_lsn and compare it to the last LSN shown in the Migration Log. If they are close, the stream is healthy.

Best practices to reduce UI freeze frequency

💡
If you need to verify migration is still running without touching the UI: open a PowerShell window and tail the log file:
Get-Content "C:\MigrationT\migration_logs\migration_$(Get-Date -f yyyyMMdd)*.log" -Wait -Tail 20
This shows live log output without touching MigrationBridge at all.

🗺️ Known Issues

Temporal table SysEnd clamping: ODBC Driver 17 cannot write year-9999 datetime boundaries. Endrias Bridge automatically clamps SysEnd = 9999-12-31 to 9998-12-31. Log lines showing WARNING: SysEnd clamped are informational — data lands correctly on target.

FILESTREAM / FILETABLE: Not supported on Azure SQL Database. Columns are migrated as VARBINARY(MAX). Files stored in FILESTREAM must be migrated separately to Azure Blob Storage.

SQL Server Agent jobs: Not available on Azure SQL (PaaS). Recreate as Azure Elastic Jobs post-migration.

Linked servers: Not available on Azure SQL. Replace with OPENDATASOURCE, external tables, or Azure Data Factory pipelines.

CLR assemblies: Require explicit enablement on Azure SQL (sp_configure 'clr enabled' is not available — submit a support request for CLR).

Cross-database queries: Azure SQL does not support 3-part cross-database names. Use elastic queries instead.

TCP 10054 (idle connection drop): Azure SQL drops idle pooled connections after ~30 minutes. Endrias Bridge detects 10054/forcibly closed errors and automatically retries once after disposing the connection pool.

IssueImpactResolution
Temporal SysEnd clampingSysEnd 9999-12-31 → 9998-12-31Informational warning — data is correct on target
FILESTREAM / FILETABLEFiles not migrated; column becomes VARBINARY(MAX)Migrate files separately to Azure Blob Storage
SQL Agent jobsNot available on Azure SQL PaaSRecreate as Azure Elastic Jobs
Linked serversNot available on Azure SQL PaaSUse OPENDATASOURCE, elastic tables, or ADF
CLR assembliesRequire PaaS support ticket to enableSubmit Azure support request if CLR required
Cross-database 3-part namesQueries fail on Azure SQLUse elastic queries or consolidate databases
TCP 10054 idle dropMid-migration connection resetAuto-retry built in — no manual action needed

📊 Type Conversions Applied (SQL Server → Azure SQL)

Source TypeTarget TypeNote
All standard T-SQL typesSame typeSame engine family — 1:1 mapping
NTEXTNVARCHAR(MAX)NTEXT is deprecated; Azure SQL still accepts it but Endrias Bridge upgrades
IMAGEVARBINARY(MAX)IMAGE is deprecated
TEXTVARCHAR(MAX)TEXT is deprecated
FILESTREAM VARBINARYVARBINARY(MAX)FILESTREAM attribute removed — files not migrated
hierarchyid / geographyTEXT or nativeMigrated natively if target supports; fallback to TEXT via .ToString()
Temporal SysEnd=9999-12-319998-12-31ODBC Driver 17 clamping — informational warning, not data loss