diff --git a/readme.md b/readme.md index 735b2be..66fa304 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.SchemaFromOpenConnection.verified.md b/src/Tests/Tests.SchemaFromOpenConnection.verified.md new file mode 100644 index 0000000..a057690 --- /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] +``` diff --git a/src/Tests/Tests.cs b/src/Tests/Tests.cs index 440c9c3..ad9c378 100644 --- a/src/Tests/Tests.cs +++ b/src/Tests/Tests.cs @@ -8,10 +8,9 @@ static Tests() => "VerifySqlServer", connection => { - var serverConnection = new ServerConnection - { - ConnectionString = connection.ConnectionString, - }; + var serverConnection = new ServerConnection(); + SqlScriptBuilder.SqlConnectionObjectField.SetValue(serverConnection, connection); + serverConnection.NonPooledConnection = true; var server = new Server(serverConnection); server.ConnectionContext.ExecuteNonQuery( """ @@ -854,4 +853,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 9ad4469..4af539c 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 bc1348b..7ffe213 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 dcb9ef2..4a26f17 100644 --- a/src/Verify.SqlServer/SchemaValidation/SqlScriptBuilder.cs +++ b/src/Verify.SqlServer/SchemaValidation/SqlScriptBuilder.cs @@ -1,8 +1,20 @@ -using System.Text.RegularExpressions; -using Microsoft.SqlServer.Management.Smo; - 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 + // 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. + 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; static SqlScriptBuilder() @@ -33,10 +45,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 +254,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 9fcf0e3..038bb59 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 IReadOnlyDictionarynet48;net8.0;net9.0;net10.0 +