Fix | Enable SqlBulkCopy to target tables in Azure Synapse Analytics dedicated SQL pools#4176
Conversation
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.)
There was a problem hiding this comment.
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 useSTRING_AGGwhenSERVERPROPERTY('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. |
Perform explicit cast of SERVERPROPERTY and QUOTENAME([name]).
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
|
@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. |
|
Thanks. Will the new pipelines also have testing for a Synapse dedicated SQL pool? |
Codecov Report✅ All modified and coverable lines are covered by tests.
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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…dedicated SQL pools (#4176)
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>
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 aSELECTstatement with a dynamically-constructed list of columns.I originally had a single
@Column_Namesvariable and I built a list of columns using a query similar to:On an Azure Synapse dedicated SQL pool, this doesn't work. The error below is thrown:
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
WHEREclause 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.