From 3942f7b4460c15865a7df1aad01761b803344cfc Mon Sep 17 00:00:00 2001
From: Edward Neal <55035479+edwardneal@users.noreply.github.com>
Date: Sun, 19 Jul 2026 22:19:24 +0100
Subject: [PATCH] Require explicit LCID to be specified in globalization
invariant mode
---
.../Data/SqlClient/Server/SqlMetaData.cs | 12 ++++++
.../src/Microsoft/Data/SqlClient/SqlUtil.cs | 4 ++
.../src/Resources/Strings.Designer.cs | 9 +++++
.../src/Resources/Strings.resx | 3 ++
.../tests/FunctionalTests/SqlMetaDataTest.cs | 38 +++++++++++++++++++
5 files changed, 66 insertions(+)
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlMetaData.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlMetaData.cs
index 2c3c6acaab..98a7bbfcfc 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlMetaData.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlMetaData.cs
@@ -536,6 +536,7 @@ internal string UdtTypeName
// Construction for all types that do not have variable attributes
private void Construct(string name, SqlDbType dbType, bool useServerDefault, bool isUniqueKey, SortOrder columnSortOrder, int sortOrdinal)
{
+ ValidateGlobalizationInvariantLcid(dbType);
AssertNameIsValid(name);
ValidateSortOrder(columnSortOrder, sortOrdinal);
@@ -586,6 +587,7 @@ private void Construct(string name, SqlDbType dbType, bool useServerDefault, boo
// Construction for all types that vary by user-specified length (not Udts)
private void Construct(string name, SqlDbType dbType, long maxLength, bool useServerDefault, bool isUniqueKey, SortOrder columnSortOrder, int sortOrdinal)
{
+ ValidateGlobalizationInvariantLcid(dbType);
AssertNameIsValid(name);
ValidateSortOrder(columnSortOrder, sortOrdinal);
@@ -929,6 +931,16 @@ private void ValidateSortOrder(SortOrder columnSortOrder, int sortOrdinal)
}
}
+ private static void ValidateGlobalizationInvariantLcid(SqlDbType dbType)
+ {
+ if (LocalAppContextSwitches.GlobalizationInvariantMode &&
+ (dbType is SqlDbType.Char or SqlDbType.VarChar or SqlDbType.Text
+ or SqlDbType.NChar or SqlDbType.NVarChar or SqlDbType.NText))
+ {
+ throw SQL.GlobalizationInvariantModeRequiresLcid();
+ }
+ }
+
///
public short Adjust(short value)
{
diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
index c9388a42f1..d420a1ca3c 100644
--- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
+++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs
@@ -204,6 +204,10 @@ internal static Exception ChangePasswordUseOfUnallowedKey(string key)
{
return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_ChangePasswordUseOfUnallowedKey, key));
}
+ internal static Exception GlobalizationInvariantModeRequiresLcid()
+ {
+ return ADP.NotSupported(StringsHelper.GetString(Strings.SQL_GlobalizationInvariantModeRequiresLcid));
+ }
internal static Exception GlobalizationInvariantModeNotSupported()
{
return ADP.NotSupported(StringsHelper.GetString(Strings.SQL_GlobalizationInvariantModeNotSupported));
diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
index 7064a6c19c..9e14a4234e 100644
--- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
+++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs
@@ -3336,6 +3336,15 @@ internal static string SQL_GlobalizationInvariantModeNotSupported {
}
}
+ ///
+ /// Looks up a localized string similar to When Globalization Invariant Mode is enabled, SqlMetaData instances describing character or text types must specify an LCID..
+ ///
+ internal static string SQL_GlobalizationInvariantModeRequiresLcid {
+ get {
+ return ResourceManager.GetString("SQL_GlobalizationInvariantModeRequiresLcid", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Instance failure..
///
diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
index e7f438873f..3fcdb6bec6 100644
--- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
+++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx
@@ -2091,6 +2091,9 @@
Exception occurred while trying to set the AppContext Switch '{0}'={1}.
+
+ When Globalization Invariant Mode is enabled, SqlMetaData instances describing character or text types must specify an LCID.
+
Globalization Invariant Mode is not supported.
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlMetaDataTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlMetaDataTest.cs
index cd316a9eab..f466338b10 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlMetaDataTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlMetaDataTest.cs
@@ -9,6 +9,7 @@
using System.Globalization;
using System.Reflection;
using Microsoft.Data.SqlClient.Server;
+using Microsoft.Data.SqlClient.Tests.Common;
using Xunit;
namespace Microsoft.Data.SqlClient.Tests
@@ -228,6 +229,43 @@ public void ConstructorWithDefaultLocale()
Assert.Equal(0, metaData.SortOrdinal);
}
+ #if NET
+ [Theory]
+ [MemberData(nameof(ConstructorCharData))]
+ [MemberData(nameof(ConstructorTextData))]
+ public void ConstructorWithDefaultLocale_ThrowsInInvariantGlobalizationMode(SqlDbType dbType)
+ {
+ using LocalAppContextSwitchesHelper helper = new();
+
+ helper.GlobalizationInvariantMode = true;
+
+ Assert.Throws(() => new SqlMetaData("col1", dbType));
+ Assert.Throws(() => new SqlMetaData("col1", dbType, true, true, SortOrder.Ascending, 0));
+ Assert.Throws(() => new SqlMetaData("col1", dbType, 0));
+ Assert.Throws(() => new SqlMetaData("col1", dbType, 0, true, true, SortOrder.Ascending, 0));
+ }
+
+ [Theory]
+ [MemberData(nameof(ConstructorCharData))]
+ public void ConstructorWithMaxLengthAndExplicitLocale_DoesNotThrowInInvariantGlobalizationMode(SqlDbType dbType)
+ {
+ using LocalAppContextSwitchesHelper helper = new();
+
+ helper.GlobalizationInvariantMode = true;
+ ConstructorWithMaxLengthAndLocale(dbType);
+ }
+
+ [Theory]
+ [MemberData(nameof(ConstructorTextData))]
+ public void ConstructorWithMaxLengthTextExplicitLocale_DoesNotThrowInInvariantGlobalizationMode(SqlDbType dbType)
+ {
+ using LocalAppContextSwitchesHelper helper = new();
+
+ helper.GlobalizationInvariantMode = true;
+ ConstructorWithMaxLengthTextAndLocale(dbType);
+ }
+ #endif
+
[Fact]
public void ConstructorWithDefaultLocaleInvalidType_Throws()
{