Skip to content

Fix | Enable SqlBulkCopy to target tables in Azure Synapse Analytics dedicated SQL pools#4176

Merged
paulmedynski merged 3 commits intodotnet:mainfrom
edwardneal:fix/issue-4149
Apr 13, 2026
Merged

Fix | Enable SqlBulkCopy to target tables in Azure Synapse Analytics dedicated SQL pools#4176
paulmedynski merged 3 commits intodotnet:mainfrom
edwardneal:fix/issue-4149

Conversation

@edwardneal
Copy link
Copy Markdown
Contributor

Description

This is designed to fix #4149, which blocked SqlBulkCopy from being used to copy data to an Azure Synapse dedicated SQL pool.

The original issue emerged as a result of #3590, which changed the query used to gather column metadata from simply running SELECT * FROM [Source] to executing a SELECT statement with a dynamically-constructed list of columns.

I originally had a single @Column_Names variable and I built a list of columns using a query similar to:

SELECT @Column_Names = COALESCE(@Column_Names + ', ', '') + QUOTENAME([name])
FROM [sys].[all_columns]
WHERE [object_id] = @Object_ID
ORDER BY [column_id] ASC

On an Azure Synapse dedicated SQL pool, this doesn't work. The error below is thrown:

Msg 104473, Level 16, State 1, Line 11
A variable that has been assigned in a SELECT statement cannot be included in a expression or assignment when used in conjunction with a from clause.

To fix this, I've replaced it with a reference to STRING_AGG.

Muddying the water slightly is legacy compatibility, since STRING_AGG was only introduced in SQL 2017 and SQL 2016 remains a supported version. I can't directly compare version numbers: SQL 2016 is version 13.x, while Synapse reports version 10.x. To work around this, I just evaluate SERVERPROPERTY('EngineEdition'), since a value of 6 indicates Azure Synapse Analytics.

We also vary the WHERE clause based upon whether or not the SQL Graph columns exist. To avoid generating a mishmash of different criteria, I've decided to break the query into its SELECT, WHERE and ORDER BY components and join them up at the end of the process.

Issues

Fixes #4149. This will need to be backported to 7.0 though.

Testing

Automated tests continue to pass, against both SQL 2016 and SQL 2025.
Manual testing against an Azure Synapse dedicated SQL pool works.

It's worth noting that we don't currently have a test job for this, that most of the existing SqlBulkCopy tests are exempted from running against this target, and that when I override the exemption I encounter a handful of unrelated issues. It might be worth adding a test environment so that we can get CI coverage.

Primary point: use STRING_AGG rather than SELECTing a value into a variable.
Secondary point: break the column name query into a SELECT, a FILTER and a SORT. This ensures that we don't use STRING_AGG on SQL Server 2016 instances (since this function was only introduced in SQL 2017.)
@edwardneal edwardneal requested a review from a team as a code owner April 9, 2026 22:07
@github-project-automation github-project-automation Bot moved this to To triage in SqlClient Board Apr 9, 2026
Comment thread src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes SqlBulkCopy destination-table metadata discovery for Azure Synapse dedicated SQL pools by avoiding the unsupported “self-referencing variable assignment” concatenation pattern and using a STRING_AGG-based query path.

Changes:

  • Update the dynamic column-name query generation in SqlBulkCopy.CreateInitialQuery() to use STRING_AGG when SERVERPROPERTY('EngineEdition') = 6 (Synapse).
  • Refactor the dynamic SQL construction into SELECT/FILTER/SORT components to keep filtering logic consistent across branches.
  • Update ManualTests expectations for connection statistics impacted by the revised metadata query.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs Adjusts metadata query generation to support Synapse (EngineEdition=6) via STRING_AGG and refactors query assembly.
src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/CopyAllFromReader.cs Updates expected SelectCount/SelectRows stats after the metadata-query change.

Comment thread src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs Outdated
Comment thread src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs Outdated
Perform explicit cast of SERVERPROPERTY and QUOTENAME([name]).
@paulmedynski paulmedynski added this to the 7.1.0-preview1 milestone Apr 10, 2026
@paulmedynski paulmedynski moved this from To triage to In review in SqlClient Board Apr 10, 2026
@paulmedynski paulmedynski added the Area\Sql Bulk Copy Issues that apply to bulk copy functionality in the driver. label Apr 10, 2026
@paulmedynski
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@paulmedynski paulmedynski added the Hotfix 7.0.1 When this PR merges, automatically open a PR to cherry-pick to the 7.0.1 branch label Apr 13, 2026
@paulmedynski
Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@paulmedynski
Copy link
Copy Markdown
Contributor

@edwardneal - Our PR pipelines don't test against SQL Server 2016 or 2017 to keep our resource footprint reasonable. Those tests only run as part of the CI pipelines, which occur post-merge to main. I can't manually run the CI pipelines against a fork, so we will have to wait until this merges to see how things work against 2016 and 2017. This won't be an issue with the new pipeline structure under development.

@paulmedynski paulmedynski self-assigned this Apr 13, 2026
@edwardneal
Copy link
Copy Markdown
Contributor Author

Thanks. Will the new pipelines also have testing for a Synapse dedicated SQL pool?

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 65.13%. Comparing base (52c7149) to head (5397173).
⚠️ Report is 6 commits behind head on main.

❗ There is a different number of reports uploaded between BASE (52c7149) and HEAD (5397173). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (52c7149) HEAD (5397173)
CI-SqlClient 1 0
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4176      +/-   ##
==========================================
- Coverage   74.27%   65.13%   -9.15%     
==========================================
  Files         279      274       -5     
  Lines       42980    65815   +22835     
==========================================
+ Hits        31922    42866   +10944     
- Misses      11058    22949   +11891     
Flag Coverage Δ
CI-SqlClient ?
PR-SqlClient-Project 65.13% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@paulmedynski paulmedynski merged commit 2b96653 into dotnet:main Apr 13, 2026
301 checks passed
@github-project-automation github-project-automation Bot moved this from In review to Done in SqlClient Board Apr 13, 2026
github-actions Bot pushed a commit that referenced this pull request Apr 13, 2026
This was referenced Apr 27, 2026
Ghostdog02 pushed a commit to Ghostdog02/Lexiq that referenced this pull request Apr 27, 2026
Updated [DotNetEnv](https://github.com/tonerdo/dotnet-env) from 3.1.1 to
3.2.0.

<details>
<summary>Release notes</summary>

_Sourced from [DotNetEnv's
releases](https://github.com/tonerdo/dotnet-env/releases)._

## 3.2.0

- Switch parsing to Superpower (from Sprache)
- Fix utf8 parsing
- Interpolated variables parsing


Commits viewable in [compare
view](tonerdo/dotnet-env@v3.1.1...v3.2.0).
</details>

Updated
[Microsoft.AspNetCore.Authentication.Google](https://github.com/dotnet/dotnet)
from 10.0.6 to 10.0.7.

<details>
<summary>Release notes</summary>

_Sourced from [Microsoft.AspNetCore.Authentication.Google's
releases](https://github.com/dotnet/dotnet/releases)._

No release notes found for this version range.

Commits viewable in [compare
view](https://github.com/dotnet/dotnet/commits).
</details>

Updated [Microsoft.Data.SqlClient](https://github.com/dotnet/sqlclient)
from 7.0.0 to 7.0.1.

<details>
<summary>Release notes</summary>

_Sourced from [Microsoft.Data.SqlClient's
releases](https://github.com/dotnet/sqlclient/releases)._

## 7.0.1

This update brings the following changes since the
[7.0.0](https://github.com/dotnet/SqlClient/blob/release/7.0/release-notes/7.0/7.0.0.md)
release:

### Fixed

- Fixed `SqlBulkCopy` failing on SQL Server 2016 with `Invalid column
name 'graph_type'` error. The column metadata query now uses dynamic SQL
so that references to the `graph_type` column (introduced in SQL Server
2017) are not compiled on older versions that lack the column.
([#​3714](dotnet/SqlClient#3714),
[#​4092](dotnet/SqlClient#4092),
[#​4147](dotnet/SqlClient#4147))

- Fixed `SqlBulkCopy` failing on Azure Synapse Analytics dedicated SQL
pools. The column-list query previously used a variable-assignment
pattern that Synapse does not support; it now uses `STRING_AGG` when
targeting Synapse (engine edition 6) and falls back to the
variable-assignment approach for SQL Server 2016 compatibility.
([#​4149](dotnet/SqlClient#4149),
[#​4176](dotnet/SqlClient#4176),
[#​4182](dotnet/SqlClient#4182))

- Fixed `SqlDataReader.GetFieldType()` and
`GetProviderSpecificFieldType()` returning `typeof(byte[])` instead of
`typeof(SqlVector<float>)` for vector float32 columns. The methods now
follow the same type-determination logic as `GetValue()`.
([#​4104](dotnet/SqlClient#4104),
[#​4105](dotnet/SqlClient#4105),
[#​4152](dotnet/SqlClient#4152))

- Added missing `System.Data.Common` (v4.3.0) NuGet package dependency
for .NET Framework consumers. The inbox `System.Data.Common` assembly on
.NET Framework predates APIs such as `IDbColumnSchemaGenerator`; without
the explicit NuGet dependency, consumers encountered `CS0012`
compilation errors when using these types through
`Microsoft.Data.SqlClient`.
([#​4063](dotnet/SqlClient#4063),
[#​4074](dotnet/SqlClient#4074))

### Changed

- Enabled the User Agent TDS feature extension unconditionally. The
`Switch.Microsoft.Data.SqlClient.EnableUserAgent` AppContext switch has
been removed; the driver now always sends User Agent information during
login. ([#​4124](dotnet/SqlClient#4124),
[#​4154](dotnet/SqlClient#4154))

- Added type forwards from the core `Microsoft.Data.SqlClient` assembly
to public types that were moved to the
`Microsoft.Data.SqlClient.Extensions.Abstractions` package:
`SqlAuthenticationMethod`, `SqlAuthenticationParameters`,
`SqlAuthenticationProvider`, `SqlAuthenticationProviderException`, and
`SqlAuthenticationToken`. This ensures binary compatibility for
assemblies compiled against earlier versions of
`Microsoft.Data.SqlClient` where these types lived in the core assembly.
([#​4067](dotnet/SqlClient#4067),
[#​4117](dotnet/SqlClient#4117))

- Fixed API documentation include paths and duplicate doc snippets.
([#​4084](dotnet/SqlClient#4084),
[#​4086](dotnet/SqlClient#4086),
[#​4107](dotnet/SqlClient#4107),
[#​4161](dotnet/SqlClient#4161))

## Contributors

We thank the following public contributors. Their efforts toward this
project are very much appreciated.

- [edwardneal](https://github.com/edwardneal)

## Target Platform Support

- .NET Framework 4.6.2+ (Windows x86, Windows x64, Windows ARM64)
- .NET 8.0+ (Windows x86, Windows x64, Windows ARM, Windows ARM64,
Linux, macOS)

### Dependencies

#### .NET 9.0

- Microsoft.Bcl.Cryptography 9.0.13
- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0
- Microsoft.Data.SqlClient.Internal.Logging 1.0.0
- Microsoft.Data.SqlClient.SNI.runtime 6.0.2
- Microsoft.Extensions.Caching.Memory 9.0.13
- Microsoft.IdentityModel.JsonWebTokens 8.16.0
- Microsoft.IdentityModel.Protocols.OpenIdConnect 8.16.0
- Microsoft.SqlServer.Server 1.0.0
- System.Configuration.ConfigurationManager 9.0.13
- System.Security.Cryptography.Pkcs 9.0.13

#### .NET 8.0

- Microsoft.Bcl.Cryptography 8.0.0
- Microsoft.Data.SqlClient.Extensions.Abstractions 1.0.0
 ... (truncated)

Commits viewable in [compare
view](dotnet/SqlClient@v7.0.0...v7.0.1).
</details>

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area\Sql Bulk Copy Issues that apply to bulk copy functionality in the driver. Hotfix 7.0.1 When this PR merges, automatically open a PR to cherry-pick to the 7.0.1 branch

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

SqlBulkCopy.WriteToServerAsync(reader) fails to write after upgrading Microsoft.Data.SqlClient to 7.0.0

5 participants