From f4c643376a1bb8941453084ca7d962d16ce78194 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 27 Mar 2026 08:30:19 +1100 Subject: [PATCH 1/5] better SqlAuthenticationMethod --- readme.md | 24 +++++++++++++++++ ...Tests.SchemaFromOpenConnection.verified.md | 21 +++++++++++++++ src/Tests/Tests.cs | 27 +++++++++++++++---- src/Verify.SqlServer/GlobalUsings.cs | 2 ++ .../SchemaValidation/SchemaSettings.cs | 2 -- .../SchemaValidation/SqlScriptBuilder.cs | 18 ++++++++++--- .../VerifySettingsExtensions.cs | 4 +-- 7 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 src/Tests/Tests.SchemaFromOpenConnection.verified.md diff --git a/readme.md b/readme.md index 735b2be1..05b7f069 100644 --- a/readme.md +++ b/readme.md @@ -50,7 +50,11 @@ This test: ```cs await Verify(connection); ``` +<<<<<<< ours snippet source | anchor +======= +snippet source | anchor +>>>>>>> theirs Will result in the following verified file: @@ -69,7 +73,11 @@ await Verify(connection) // include only tables and views .SchemaIncludes(DbObjects.Tables | DbObjects.Views); ``` +<<<<<<< ours snippet source | anchor +======= +snippet source | anchor +>>>>>>> theirs Available values: @@ -107,7 +115,11 @@ await Verify(connection) _ => _ is TableViewBase || _.Name == "MyTrigger"); ``` +<<<<<<< ours snippet source | anchor +======= +snippet source | anchor +>>>>>>> theirs @@ -129,7 +141,11 @@ command.CommandText = "select Value from MyTable"; var value = await command.ExecuteScalarAsync(); await Verify(value!); ``` +<<<<<<< ours snippet source | anchor +======= +snippet source | anchor +>>>>>>> theirs Will result in the following verified file: @@ -184,7 +200,11 @@ await Verify( sqlEntries = entries }); ``` +<<<<<<< ours snippet source | anchor +======= +snippet source | anchor +>>>>>>> theirs @@ -212,7 +232,11 @@ var sqlErrorsViaType = entries .Select(_ => _.Data) .OfType(); ``` +<<<<<<< ours snippet source | anchor +======= +snippet source | anchor +>>>>>>> theirs diff --git a/src/Tests/Tests.SchemaFromOpenConnection.verified.md b/src/Tests/Tests.SchemaFromOpenConnection.verified.md new file mode 100644 index 00000000..859a9514 --- /dev/null +++ b/src/Tests/Tests.SchemaFromOpenConnection.verified.md @@ -0,0 +1,21 @@ +## Tables + +### MyTable + +```sql +CREATE TABLE [dbo].[MyTable]( + [Value] [int] NULL +) ON [PRIMARY] + +CREATE NONCLUSTERED INDEX [MyIndex] ON [dbo].[MyTable] +( + [Value] ASC +) ON [PRIMARY] + +CREATE TRIGGER MyTrigger +ON MyTable +AFTER UPDATE +AS RAISERROR ('Notify Customer Relations', 16, 10); + +ALTER TABLE [dbo].[MyTable] ENABLE TRIGGER [MyTrigger] +``` \ No newline at end of file diff --git a/src/Tests/Tests.cs b/src/Tests/Tests.cs index 440c9c3d..263a028d 100644 --- a/src/Tests/Tests.cs +++ b/src/Tests/Tests.cs @@ -1,6 +1,9 @@ [TestFixture] public class Tests { + static readonly FieldInfo sqlConnectionObjectField = + typeof(ConnectionManager).GetField("m_SqlConnectionObject", BindingFlags.NonPublic | BindingFlags.Instance)!; + static SqlInstance sqlInstance; static Tests() => @@ -8,10 +11,9 @@ static Tests() => "VerifySqlServer", connection => { - var serverConnection = new ServerConnection - { - ConnectionString = connection.ConnectionString, - }; + var serverConnection = new ServerConnection(); + sqlConnectionObjectField.SetValue(serverConnection, connection); + serverConnection.NonPooledConnection = true; var server = new Server(serverConnection); server.ConnectionContext.ExecuteNonQuery( """ @@ -854,4 +856,19 @@ await Verify(connection) .SchemaIncludes(DbObjects.Tables | DbObjects.Views) .SchemaFilter(_ => _.Name is "MyTable" or "MyView" or "MyProcedure"); } -} \ No newline at end of file + + // Verifies the workaround for SMO 181.15.0 + SqlClient 7.0 TypeLoadException. + // SMO's ServerConnection(SqlConnection) constructor references SqlAuthenticationMethod + // which moved from Microsoft.Data.SqlClient to Extensions.Abstractions in SqlClient 7.0. + // The fix avoids that constructor by using reflection to set the SqlConnection directly. + [Test] + public async Task SchemaFromOpenConnection() + { + await using var database = await sqlInstance.Build(); + await using var connection = new SqlConnection(database.ConnectionString); + await connection.OpenAsync(); + await Verify(connection) + .SchemaFilter(_ => _.Name == "MyTable") + .SchemaIncludes(DbObjects.Tables); + } +} diff --git a/src/Verify.SqlServer/GlobalUsings.cs b/src/Verify.SqlServer/GlobalUsings.cs index 9ad4469e..4af539c6 100644 --- a/src/Verify.SqlServer/GlobalUsings.cs +++ b/src/Verify.SqlServer/GlobalUsings.cs @@ -2,8 +2,10 @@ global using System.Data.Common; global using System.Data.SqlTypes; global using System.Globalization; +global using System.Text.RegularExpressions; global using Microsoft.Data.SqlClient; global using Microsoft.Extensions.DiagnosticAdapter; global using Microsoft.SqlServer.Management.Common; +global using Microsoft.SqlServer.Management.Smo; global using Microsoft.SqlServer.TransactSql.ScriptDom; global using VerifyTests.SqlServer; \ No newline at end of file diff --git a/src/Verify.SqlServer/SchemaValidation/SchemaSettings.cs b/src/Verify.SqlServer/SchemaValidation/SchemaSettings.cs index bc1348ba..7ffe2139 100644 --- a/src/Verify.SqlServer/SchemaValidation/SchemaSettings.cs +++ b/src/Verify.SqlServer/SchemaValidation/SchemaSettings.cs @@ -1,5 +1,3 @@ -using Microsoft.SqlServer.Management.Smo; - class SchemaSettings { public DbObjects Includes { get; set; } = DbObjects.All; diff --git a/src/Verify.SqlServer/SchemaValidation/SqlScriptBuilder.cs b/src/Verify.SqlServer/SchemaValidation/SqlScriptBuilder.cs index dcb9ef23..2e194d82 100644 --- a/src/Verify.SqlServer/SchemaValidation/SqlScriptBuilder.cs +++ b/src/Verify.SqlServer/SchemaValidation/SqlScriptBuilder.cs @@ -1,8 +1,16 @@ -using System.Text.RegularExpressions; -using Microsoft.SqlServer.Management.Smo; - class SqlScriptBuilder(SchemaSettings settings) { + // SMO 181.15.0 ServerConnection(SqlConnection) constructor calls InitFromSqlConnection + // which references SqlAuthenticationMethod — a type moved from Microsoft.Data.SqlClient + // to Microsoft.Data.SqlClient.Extensions.Abstractions in SqlClient 7.0. The CLR can't + // resolve the type in the original assembly, causing a TypeLoadException. + // + // Workaround: construct ServerConnection() with default constructor (no InitFromSqlConnection), + // then set the internal m_SqlConnectionObject field via reflection to reuse the open connection. + // SMO detects the connection is already open and uses it directly. + static readonly FieldInfo sqlConnectionObjectField = + typeof(ConnectionManager).GetField("m_SqlConnectionObject", BindingFlags.NonPublic | BindingFlags.Instance)!; + static Dictionary tableSettingsToScrubLookup; static SqlScriptBuilder() @@ -33,10 +41,12 @@ public string BuildContent(SqlConnection connection) var builder = new SqlConnectionStringBuilder(connection.ConnectionString); var serverConnection = new ServerConnection { + NonPooledConnection = true, ConnectionString = connection.ConnectionString, }; try { + sqlConnectionObjectField.SetValue(serverConnection, connection); var server = new Server(serverConnection); return BuildContent(server, builder); } @@ -240,4 +250,4 @@ script is "SET QUOTED_IDENTIFIER OFF" or "SET ANSI_PADDING ON" or "SET ANSI_PADDING OFF"; -} \ No newline at end of file +} diff --git a/src/Verify.SqlServer/SchemaValidation/VerifySettingsExtensions.cs b/src/Verify.SqlServer/SchemaValidation/VerifySettingsExtensions.cs index 9fcf0e36..038bb594 100644 --- a/src/Verify.SqlServer/SchemaValidation/VerifySettingsExtensions.cs +++ b/src/Verify.SqlServer/SchemaValidation/VerifySettingsExtensions.cs @@ -1,5 +1,3 @@ -using Microsoft.SqlServer.Management.Smo; - namespace VerifyTests; public static partial class VerifySettingsSqlExtensions @@ -81,4 +79,4 @@ internal static SchemaSettings GetSchemaSettings(this IReadOnlyDictionary Date: Thu, 26 Mar 2026 21:30:42 +0000 Subject: [PATCH 2/5] Docs changes --- readme.md | 36 ++++--------------- ...Tests.SchemaFromOpenConnection.verified.md | 4 +-- 2 files changed, 8 insertions(+), 32 deletions(-) diff --git a/readme.md b/readme.md index 05b7f069..c2be0cc0 100644 --- a/readme.md +++ b/readme.md @@ -50,11 +50,7 @@ This test: ```cs await Verify(connection); ``` -<<<<<<< ours -snippet source | anchor -======= -snippet source | anchor ->>>>>>> theirs +snippet source | anchor Will result in the following verified file: @@ -73,11 +69,7 @@ await Verify(connection) // include only tables and views .SchemaIncludes(DbObjects.Tables | DbObjects.Views); ``` -<<<<<<< ours -snippet source | anchor -======= -snippet source | anchor ->>>>>>> theirs +snippet source | anchor Available values: @@ -115,11 +107,7 @@ await Verify(connection) _ => _ is TableViewBase || _.Name == "MyTrigger"); ``` -<<<<<<< ours -snippet source | anchor -======= -snippet source | anchor ->>>>>>> theirs +snippet source | anchor @@ -141,11 +129,7 @@ command.CommandText = "select Value from MyTable"; var value = await command.ExecuteScalarAsync(); await Verify(value!); ``` -<<<<<<< ours -snippet source | anchor -======= -snippet source | anchor ->>>>>>> theirs +snippet source | anchor Will result in the following verified file: @@ -200,11 +184,7 @@ await Verify( sqlEntries = entries }); ``` -<<<<<<< ours -snippet source | anchor -======= -snippet source | anchor ->>>>>>> theirs +snippet source | anchor @@ -232,11 +212,7 @@ var sqlErrorsViaType = entries .Select(_ => _.Data) .OfType(); ``` -<<<<<<< ours -snippet source | anchor -======= -snippet source | anchor ->>>>>>> theirs +snippet source | anchor diff --git a/src/Tests/Tests.SchemaFromOpenConnection.verified.md b/src/Tests/Tests.SchemaFromOpenConnection.verified.md index 859a9514..a0576904 100644 --- a/src/Tests/Tests.SchemaFromOpenConnection.verified.md +++ b/src/Tests/Tests.SchemaFromOpenConnection.verified.md @@ -1,4 +1,4 @@ -## Tables +## Tables ### MyTable @@ -18,4 +18,4 @@ AFTER UPDATE AS RAISERROR ('Notify Customer Relations', 16, 10); ALTER TABLE [dbo].[MyTable] ENABLE TRIGGER [MyTrigger] -``` \ No newline at end of file +``` From dd6d00f92296aab0be5d76dfe5a32eb4dfa335ec Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 27 Mar 2026 08:32:03 +1100 Subject: [PATCH 3/5] Update readme.md --- readme.md | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/readme.md b/readme.md index 05b7f069..735b2be1 100644 --- a/readme.md +++ b/readme.md @@ -50,11 +50,7 @@ This test: ```cs await Verify(connection); ``` -<<<<<<< ours snippet source | anchor -======= -snippet source | anchor ->>>>>>> theirs Will result in the following verified file: @@ -73,11 +69,7 @@ await Verify(connection) // include only tables and views .SchemaIncludes(DbObjects.Tables | DbObjects.Views); ``` -<<<<<<< ours snippet source | anchor -======= -snippet source | anchor ->>>>>>> theirs Available values: @@ -115,11 +107,7 @@ await Verify(connection) _ => _ is TableViewBase || _.Name == "MyTrigger"); ``` -<<<<<<< ours snippet source | anchor -======= -snippet source | anchor ->>>>>>> theirs @@ -141,11 +129,7 @@ command.CommandText = "select Value from MyTable"; var value = await command.ExecuteScalarAsync(); await Verify(value!); ``` -<<<<<<< ours snippet source | anchor -======= -snippet source | anchor ->>>>>>> theirs Will result in the following verified file: @@ -200,11 +184,7 @@ await Verify( sqlEntries = entries }); ``` -<<<<<<< ours snippet source | anchor -======= -snippet source | anchor ->>>>>>> theirs @@ -232,11 +212,7 @@ var sqlErrorsViaType = entries .Select(_ => _.Data) .OfType(); ``` -<<<<<<< ours snippet source | anchor -======= -snippet source | anchor ->>>>>>> theirs From df3c51c8c70cfd89eacf1e01b0d7535aaaec95bf Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 26 Mar 2026 21:32:51 +0000 Subject: [PATCH 4/5] Docs changes --- readme.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index 735b2be1..c2be0cc0 100644 --- a/readme.md +++ b/readme.md @@ -50,7 +50,7 @@ This test: ```cs await Verify(connection); ``` -snippet source | anchor +snippet source | anchor Will result in the following verified file: @@ -69,7 +69,7 @@ await Verify(connection) // include only tables and views .SchemaIncludes(DbObjects.Tables | DbObjects.Views); ``` -snippet source | anchor +snippet source | anchor Available values: @@ -107,7 +107,7 @@ await Verify(connection) _ => _ is TableViewBase || _.Name == "MyTrigger"); ``` -snippet source | anchor +snippet source | anchor @@ -129,7 +129,7 @@ command.CommandText = "select Value from MyTable"; var value = await command.ExecuteScalarAsync(); await Verify(value!); ``` -snippet source | anchor +snippet source | anchor Will result in the following verified file: @@ -184,7 +184,7 @@ await Verify( sqlEntries = entries }); ``` -snippet source | anchor +snippet source | anchor @@ -212,7 +212,7 @@ var sqlErrorsViaType = entries .Select(_ => _.Data) .OfType(); ``` -snippet source | anchor +snippet source | anchor From ab3adca17d443a37af0cbc66e605775c00cdb4a8 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 27 Mar 2026 09:02:29 +1100 Subject: [PATCH 5/5] . --- readme.md | 12 ++++++------ src/Tests/Tests.cs | 5 +---- .../SchemaValidation/SqlScriptBuilder.cs | 10 +++++++--- src/Verify.SqlServer/Verify.SqlServer.csproj | 1 + 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/readme.md b/readme.md index 735b2be1..66fa3045 100644 --- a/readme.md +++ b/readme.md @@ -50,7 +50,7 @@ This test: ```cs await Verify(connection); ``` -snippet source | anchor +snippet source | anchor Will result in the following verified file: @@ -69,7 +69,7 @@ await Verify(connection) // include only tables and views .SchemaIncludes(DbObjects.Tables | DbObjects.Views); ``` -snippet source | anchor +snippet source | anchor Available values: @@ -107,7 +107,7 @@ await Verify(connection) _ => _ is TableViewBase || _.Name == "MyTrigger"); ``` -snippet source | anchor +snippet source | anchor @@ -129,7 +129,7 @@ command.CommandText = "select Value from MyTable"; var value = await command.ExecuteScalarAsync(); await Verify(value!); ``` -snippet source | anchor +snippet source | anchor Will result in the following verified file: @@ -184,7 +184,7 @@ await Verify( sqlEntries = entries }); ``` -snippet source | anchor +snippet source | anchor @@ -212,7 +212,7 @@ var sqlErrorsViaType = entries .Select(_ => _.Data) .OfType(); ``` -snippet source | anchor +snippet source | anchor diff --git a/src/Tests/Tests.cs b/src/Tests/Tests.cs index 263a028d..ad9c3783 100644 --- a/src/Tests/Tests.cs +++ b/src/Tests/Tests.cs @@ -1,9 +1,6 @@ [TestFixture] public class Tests { - static readonly FieldInfo sqlConnectionObjectField = - typeof(ConnectionManager).GetField("m_SqlConnectionObject", BindingFlags.NonPublic | BindingFlags.Instance)!; - static SqlInstance sqlInstance; static Tests() => @@ -12,7 +9,7 @@ static Tests() => connection => { var serverConnection = new ServerConnection(); - sqlConnectionObjectField.SetValue(serverConnection, connection); + SqlScriptBuilder.SqlConnectionObjectField.SetValue(serverConnection, connection); serverConnection.NonPooledConnection = true; var server = new Server(serverConnection); server.ConnectionContext.ExecuteNonQuery( diff --git a/src/Verify.SqlServer/SchemaValidation/SqlScriptBuilder.cs b/src/Verify.SqlServer/SchemaValidation/SqlScriptBuilder.cs index 2e194d82..4a26f17b 100644 --- a/src/Verify.SqlServer/SchemaValidation/SqlScriptBuilder.cs +++ b/src/Verify.SqlServer/SchemaValidation/SqlScriptBuilder.cs @@ -1,5 +1,8 @@ class SqlScriptBuilder(SchemaSettings settings) { + // TODO: when Microsoft.Data.SqlClient 7.0.1 adds TypeForwardedTo for SqlAuthenticationMethod, + // revert to using new ServerConnection(SqlConnection) and remove this reflection workaround. + // // SMO 181.15.0 ServerConnection(SqlConnection) constructor calls InitFromSqlConnection // which references SqlAuthenticationMethod — a type moved from Microsoft.Data.SqlClient // to Microsoft.Data.SqlClient.Extensions.Abstractions in SqlClient 7.0. The CLR can't @@ -8,8 +11,9 @@ class SqlScriptBuilder(SchemaSettings settings) // Workaround: construct ServerConnection() with default constructor (no InitFromSqlConnection), // then set the internal m_SqlConnectionObject field via reflection to reuse the open connection. // SMO detects the connection is already open and uses it directly. - static readonly FieldInfo sqlConnectionObjectField = - typeof(ConnectionManager).GetField("m_SqlConnectionObject", BindingFlags.NonPublic | BindingFlags.Instance)!; + internal static readonly FieldInfo SqlConnectionObjectField = + typeof(ConnectionManager).GetField("m_SqlConnectionObject", BindingFlags.NonPublic | BindingFlags.Instance) ?? + throw new("Could not find field m_SqlConnectionObject on ConnectionManager. The SMO internals may have changed."); static Dictionary tableSettingsToScrubLookup; @@ -46,7 +50,7 @@ public string BuildContent(SqlConnection connection) }; try { - sqlConnectionObjectField.SetValue(serverConnection, connection); + SqlConnectionObjectField.SetValue(serverConnection, connection); var server = new Server(serverConnection); return BuildContent(server, builder); } diff --git a/src/Verify.SqlServer/Verify.SqlServer.csproj b/src/Verify.SqlServer/Verify.SqlServer.csproj index 4d92d575..3c6636f3 100644 --- a/src/Verify.SqlServer/Verify.SqlServer.csproj +++ b/src/Verify.SqlServer/Verify.SqlServer.csproj @@ -3,6 +3,7 @@ net48;net8.0;net9.0;net10.0 +