From 5268b9e5324b0a43cebe0d66cea69d3214da4877 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Fri, 28 Aug 2026 11:33:16 +0200 Subject: [PATCH 1/2] Invalidate cached Owner property after Database.SetOwner SetOwnerImpl only executed the ALTER AUTHORIZATION statement without touching the property bag, so Database.Owner kept returning the previous owner until Refresh() was called (issue #40). Instead of storing the caller-provided login name (which may differ from the canonical name the server stores, e.g. by casing on a case-insensitive server), mark the Owner property as not retrieved and the property bag as lazy - the same pattern CleanObject uses for the default language properties - so the next read of Owner fetches the canonical value from the server. The existing Database_CanChangeOwner tests worked around the bug by calling Refresh() before asserting; they now also assert the value before Refresh(). Fixes #40 Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 2 ++ .../Smo/ScriptingTests/Database_SmoTestSuite.cs | 2 ++ .../SqlServer/Management/Smo/DatabaseBase.cs | 12 ++++++++++++ 3 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f22ca69..af8c04e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ Update this document for externally visible changes. Put most recent changes first. Once we push a new version to nuget.org add a double hash header for that version. +- Fix `Database.Owner` returning the previous owner after `Database.SetOwner()` until `Refresh()` was called. The cached property is now invalidated and re-fetched from the server on next access. + ## 181.37.1 - Add `ServerConnection.Edition` and `ServerConnection.EditionID` to expose the values of `SERVERPROPERTY('Edition')` and `SERVERPROPERTY('EditionID')`. diff --git a/src/FunctionalTest/Smo/ScriptingTests/Database_SmoTestSuite.cs b/src/FunctionalTest/Smo/ScriptingTests/Database_SmoTestSuite.cs index e654e4b1..cc853ffe 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/Database_SmoTestSuite.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/Database_SmoTestSuite.cs @@ -1190,6 +1190,7 @@ public void Database_CanChangeOwner() { database.SetOwner(login.Name); + Assert.That(database.Owner, Is.EqualTo(login.Name), "Owner of database should reflect the new owner immediately after calling SetOwner, without needing Refresh"); database.Refresh(); Assert.That(database.Owner, Is.EqualTo(login.Name), "Owner of database was not changed to '{0}' after calling SetOwner"); } @@ -1227,6 +1228,7 @@ public void Database_CanChangeOwner_WithExistingUser() try { database.SetOwner(login.Name, dropExistingUser: true); + Assert.That(database.Owner, Is.EqualTo(login.Name), "Owner of database should reflect the new owner immediately after calling SetOwner, without needing Refresh"); database.Refresh(); Assert.That(database.Owner, Is.EqualTo(login.Name), "Owner of database was not changed to '{0}' after calling SetOwner"); } diff --git a/src/Microsoft/SqlServer/Management/Smo/DatabaseBase.cs b/src/Microsoft/SqlServer/Management/Smo/DatabaseBase.cs index ab4edf06..7c04a5f2 100644 --- a/src/Microsoft/SqlServer/Management/Smo/DatabaseBase.cs +++ b/src/Microsoft/SqlServer/Management/Smo/DatabaseBase.cs @@ -1369,6 +1369,18 @@ private void SetOwnerImpl(string loginName, bool dropExistingUser) this.ScriptChangeOwner(query, loginName); this.ExecutionManager.ExecuteNonQuery(query); + + if (!this.ExecutionManager.Recording) + { + //The query only propagates the new owner to the server, so the cached + //Owner value is stale now. Invalidate it so it is retrieved from the + //server on next access. We don't store the caller-provided name because + //the server may canonicalize it differently (e.g. different casing on + //a case-insensitive server). + Property owner = this.Properties.Get("Owner"); + owner.SetRetrieved(false); + this.propertyBagState = PropertyBagState.Lazy; + } } } catch (Exception e) From 46c5dedb1d6bd3ba77a69bb4a6eefbfd9fb8ffc0 Mon Sep 17 00:00:00 2001 From: Andreas Jordan Date: Fri, 9 Oct 2026 18:11:11 +0200 Subject: [PATCH 2/2] Assert the server's spelling of the owner after SetOwner with different casing The regression assertions only passed the login's stored spelling, so an implementation that copied the caller-provided name into the property bag would pass them as well. On a case-insensitive server, Database_CanChangeOwner now also calls SetOwner with the upper-cased login name and asserts that Owner returns the name as stored on the server, without Refresh(). Co-Authored-By: Claude Opus 5.5 --- .../Smo/ScriptingTests/Database_SmoTestSuite.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/FunctionalTest/Smo/ScriptingTests/Database_SmoTestSuite.cs b/src/FunctionalTest/Smo/ScriptingTests/Database_SmoTestSuite.cs index cc853ffe..1518d157 100644 --- a/src/FunctionalTest/Smo/ScriptingTests/Database_SmoTestSuite.cs +++ b/src/FunctionalTest/Smo/ScriptingTests/Database_SmoTestSuite.cs @@ -1193,6 +1193,14 @@ public void Database_CanChangeOwner() Assert.That(database.Owner, Is.EqualTo(login.Name), "Owner of database should reflect the new owner immediately after calling SetOwner, without needing Refresh"); database.Refresh(); Assert.That(database.Owner, Is.EqualTo(login.Name), "Owner of database was not changed to '{0}' after calling SetOwner"); + + if (!database.Parent.Information.IsCaseSensitive) + { + //The server resolves the login case-insensitively and keeps its own spelling, + //so Owner has to return the stored name and not the casing passed to SetOwner + database.SetOwner(login.Name.ToUpperInvariant()); + Assert.That(database.Owner, Is.EqualTo(login.Name), "Owner of database should return the login name as stored on the server, not the casing passed to SetOwner"); + } } finally {