Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project>
<PropertyGroup>
<NoWarn>CS1591;CS0649;CS8632;NU1608;NU1109</NoWarn>
<Version>12.1.1</Version>
<Version>12.1.2</Version>
<LangVersion>preview</LangVersion>
<AssemblyVersion>1.0.0</AssemblyVersion>
<PackageTags>SqlServer, Verify</PackageTags>
Expand Down
31 changes: 27 additions & 4 deletions src/Tests/Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -852,10 +852,11 @@ await Verify(connection)
.SchemaFilter(_ => _.Name is "MyTable" or "MyView" or "MyProcedure");
}

// 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.
// Exercises Verify(SqlConnection) with an already-open connection.
// Note: this test uses LocalDb (Windows auth) so it passes even if the
// SqlAuthenticationMethod workaround is removed — Windows auth does not
// trigger the SMO type-load for SqlAuthenticationMethod.
// See SqlConnectionObjectFieldWorkaround for the regression guard.
[Test]
public async Task SchemaFromOpenConnection()
{
Expand All @@ -866,4 +867,26 @@ await Verify(connection)
.SchemaFilter(_ => _.Name == "MyTable")
.SchemaIncludes(DbObjects.Tables);
}

// Regression: commit fbfa399 removed SqlConnectionObjectField from SqlScriptBuilder,
// which broke Verify(SqlConnection) for connections using SQL Server authentication.
// When SMO 181.x + SqlClient 7.x opens a new connection from a SQL-auth connection
// string it tries to load SqlAuthenticationMethod — a type moved in SqlClient 7.0 —
// causing a TypeLoadException surfaced as "Login failed for user 'sa'".
// The fix injects the already-open SqlConnection directly into SMO via reflection,
// so SMO reuses it and never runs the SQL-auth type-load code path.
[Test]
public void SqlConnectionObjectFieldWorkaround()
{
var field = SqlScriptBuilder.SqlConnectionObjectField;
Assert.That(field, Is.Not.Null,
"SqlConnectionObjectField must exist to work around SMO+SqlClient 7.x TypeLoadException for SQL Server auth connections");
Assert.That(field.Name, Is.EqualTo("m_SqlConnectionObject"));

// Verify the field can actually be set on a ServerConnection instance
var serverConnection = new ServerConnection { NonPooledConnection = true };
using var sqlConnection = new SqlConnection("Server=.;Database=test;Integrated Security=True");
field.SetValue(serverConnection, sqlConnection);
Assert.That(field.GetValue(serverConnection), Is.SameAs(sqlConnection));
}
}
1 change: 1 addition & 0 deletions src/Verify.SqlServer/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
global using System.Data;
global using System.Reflection;
global using System.Data.Common;
global using System.Data.SqlTypes;
global using System.Globalization;
Expand Down
16 changes: 16 additions & 0 deletions src/Verify.SqlServer/SchemaValidation/SqlScriptBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
class SqlScriptBuilder(SchemaSettings settings)
{
// TODO: when Microsoft.Data.SqlClient 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<string, string> tableSettingsToScrubLookup;

static SqlScriptBuilder()
Expand Down Expand Up @@ -35,6 +50,7 @@ public string BuildContent(SqlConnection connection)
};
try
{
SqlConnectionObjectField.SetValue(serverConnection, connection);
var server = new Server(serverConnection);
return BuildContent(server, builder);
}
Expand Down
Loading