From 8f795d48621f7448d532a998b52a78e86df290e7 Mon Sep 17 00:00:00 2001 From: Dirk Seefeld Date: Fri, 24 Apr 2026 20:42:17 +0200 Subject: [PATCH 1/6] fix raw sql with ISqlSyntaxProvider name escaping --- ...FixLabelDataTypeDbTypeFromConfiguration.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_4_0/FixLabelDataTypeDbTypeFromConfiguration.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_4_0/FixLabelDataTypeDbTypeFromConfiguration.cs index 1c9f8b9c5060..f17c64c059de 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_4_0/FixLabelDataTypeDbTypeFromConfiguration.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_4_0/FixLabelDataTypeDbTypeFromConfiguration.cs @@ -3,6 +3,7 @@ using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_17_4_0; @@ -67,20 +68,21 @@ internal static async Task ExecuteMigration(IUmbracoDatabase database, IDataType // could exceed the default command timeout, so extend it here. EnsureLongCommandTimeout(database); + ISqlSyntaxProvider syntax = database.SqlContext.SqlSyntax; var sql = $@" -UPDATE umbracoPropertyData -SET textValue = varcharValue, varcharValue = NULL -WHERE propertyTypeId IN ( - SELECT id - FROM cmsPropertyType - WHERE dataTypeId IN ( - SELECT nodeId - FROM umbracoDataType - WHERE propertyEditorAlias = '{Constants.PropertyEditors.Aliases.Label}' - AND dbType = '{nameof(ValueStorageType.Ntext)}' +UPDATE {syntax.GetQuotedTableName("umbracoPropertyData")} +SET {syntax.GetQuotedColumnName("textValue")} = {syntax.GetQuotedColumnName("varcharValue")}, {syntax.GetQuotedColumnName("varcharValue")} = NULL +WHERE {syntax.GetQuotedColumnName("propertyTypeId")} IN ( + SELECT {syntax.GetQuotedColumnName("id")} + FROM {syntax.GetQuotedTableName("cmsPropertyType")} + WHERE {syntax.GetQuotedColumnName("dataTypeId")} IN ( + SELECT {syntax.GetQuotedColumnName("nodeId")} + FROM {syntax.GetQuotedTableName("umbracoDataType")} + WHERE {syntax.GetQuotedColumnName("propertyEditorAlias")} = '{Constants.PropertyEditors.Aliases.Label}' + AND {syntax.GetQuotedColumnName("dbType")} = '{nameof(ValueStorageType.Ntext)}' ) ) -AND varcharValue IS NOT NULL"; +AND {syntax.GetQuotedColumnName("varcharValue")} IS NOT NULL"; await database.ExecuteAsync(sql); } } From 7c5492bf72b8d6f3fc67d66d5f3d2550a1bfed73 Mon Sep 17 00:00:00 2001 From: Dirk Seefeld Date: Fri, 24 Apr 2026 23:25:36 +0200 Subject: [PATCH 2/6] reduce hard coded strings --- ...FixLabelDataTypeDbTypeFromConfiguration.cs | 23 ++++++++++--------- .../Persistence/Dtos/DataTypeDto.cs | 6 +++-- .../Persistence/Dtos/PropertyDataDto.cs | 6 +++-- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_4_0/FixLabelDataTypeDbTypeFromConfiguration.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_4_0/FixLabelDataTypeDbTypeFromConfiguration.cs index f17c64c059de..9523da436355 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_4_0/FixLabelDataTypeDbTypeFromConfiguration.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_4_0/FixLabelDataTypeDbTypeFromConfiguration.cs @@ -3,6 +3,7 @@ using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Infrastructure.Persistence.Dtos; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; namespace Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_17_4_0; @@ -70,19 +71,19 @@ internal static async Task ExecuteMigration(IUmbracoDatabase database, IDataType ISqlSyntaxProvider syntax = database.SqlContext.SqlSyntax; var sql = $@" -UPDATE {syntax.GetQuotedTableName("umbracoPropertyData")} -SET {syntax.GetQuotedColumnName("textValue")} = {syntax.GetQuotedColumnName("varcharValue")}, {syntax.GetQuotedColumnName("varcharValue")} = NULL -WHERE {syntax.GetQuotedColumnName("propertyTypeId")} IN ( - SELECT {syntax.GetQuotedColumnName("id")} - FROM {syntax.GetQuotedTableName("cmsPropertyType")} - WHERE {syntax.GetQuotedColumnName("dataTypeId")} IN ( - SELECT {syntax.GetQuotedColumnName("nodeId")} - FROM {syntax.GetQuotedTableName("umbracoDataType")} - WHERE {syntax.GetQuotedColumnName("propertyEditorAlias")} = '{Constants.PropertyEditors.Aliases.Label}' - AND {syntax.GetQuotedColumnName("dbType")} = '{nameof(ValueStorageType.Ntext)}' +UPDATE {syntax.GetQuotedTableName(PropertyDataDto.TableName)} +SET {syntax.GetQuotedColumnName(PropertyDataDto.TextValueColumnName)} = {syntax.GetQuotedColumnName(PropertyDataDto.VarcharValueColumnName)}, {syntax.GetQuotedColumnName(PropertyDataDto.VarcharValueColumnName)} = NULL +WHERE {syntax.GetQuotedColumnName(PropertyDataDto.PropertyTypeIdColumnName)} IN ( + SELECT {syntax.GetQuotedColumnName(PropertyDataDto.PrimaryKeyColumnName)} + FROM {syntax.GetQuotedTableName(PropertyTypeDto.TableName)} + WHERE {syntax.GetQuotedColumnName(PropertyTypeDto.DataTypeIdColumnName)} IN ( + SELECT {syntax.GetQuotedColumnName(DataTypeDto.PrimaryKeyColumnName)} + FROM {syntax.GetQuotedTableName(DataTypeDto.TableName)} + WHERE {syntax.GetQuotedColumnName(DataTypeDto.EditorAliasColumnName)} = '{Constants.PropertyEditors.Aliases.Label}' + AND {syntax.GetQuotedColumnName(DataTypeDto.DbTypeColumnName)} = '{nameof(ValueStorageType.Ntext)}' ) ) -AND {syntax.GetQuotedColumnName("varcharValue")} IS NOT NULL"; +AND {syntax.GetQuotedColumnName(PropertyDataDto.VarcharValueColumnName)} IS NOT NULL"; await database.ExecuteAsync(sql); } } diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/DataTypeDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/DataTypeDto.cs index 8b22a66c1108..96ca46795237 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/DataTypeDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/DataTypeDto.cs @@ -14,6 +14,8 @@ public class DataTypeDto { public const string TableName = Constants.DatabaseSchema.Tables.DataType; public const string PrimaryKeyColumnName = Constants.DatabaseSchema.Columns.NodeIdName; + public const string EditorAliasColumnName = "propertyEditorAlias"; + public const string DbTypeColumnName = "dbType"; /// /// Gets or sets the identifier of the associated node. @@ -28,7 +30,7 @@ public class DataTypeDto /// Gets or sets the alias of the property editor associated with this data type. /// /// TODO: should this have a length - [Column("propertyEditorAlias")] + [Column(EditorAliasColumnName)] public string EditorAlias { get; set; } = null!; /// @@ -41,7 +43,7 @@ public class DataTypeDto /// /// Gets or sets the type of the database column used to store values for this data type. /// - [Column("dbType")] + [Column(DbTypeColumnName)] [Length(50)] public string DbType { get; set; } = null!; diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyDataDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyDataDto.cs index 229eef7d268d..03092c456c7c 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyDataDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/PropertyDataDto.cs @@ -14,6 +14,8 @@ internal sealed class PropertyDataDto public const string PrimaryKeyColumnName = Constants.DatabaseSchema.Columns.PrimaryKeyNameId; public const string PropertyTypeIdColumnName = "propertyTypeId"; public const string VersionIdColumnName = "versionId"; + public const string TextValueColumnName = "textValue"; + public const string VarcharValueColumnName = "varcharValue"; public const int VarcharLength = 512; public const int SegmentLength = 256; @@ -93,7 +95,7 @@ public decimal? DecimalValue /// Gets or sets the string value stored in the varcharValue column for this property data record. /// This typically contains the value of a property when stored as a variable-length string. /// - [Column("varcharValue")] + [Column(VarcharValueColumnName)] [NullSetting(NullSetting = NullSettings.Null)] [Length(VarcharLength)] public string? VarcharValue { get; set; } @@ -101,7 +103,7 @@ public decimal? DecimalValue /// /// Gets or sets the text value associated with the property data in the database. /// - [Column("textValue")] + [Column(TextValueColumnName)] [NullSetting(NullSetting = NullSettings.Null)] [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] public string? TextValue { get; set; } From 0ec18adaa6790b569c0425e4985d441f96112202 Mon Sep 17 00:00:00 2001 From: Dirk Seefeld Date: Sat, 25 Apr 2026 18:49:50 +0200 Subject: [PATCH 3/6] Fix Raw Sql in MemberFilterRepository --- .../Dtos/ExternalMember2MemberGroupDto.cs | 2 +- .../Persistence/Dtos/Member2MemberGroupDto.cs | 2 +- .../Persistence/Dtos/MemberDto.cs | 2 +- .../Implement/MemberFilterRepository.cs | 134 ++++++++++-------- 4 files changed, 77 insertions(+), 63 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalMember2MemberGroupDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalMember2MemberGroupDto.cs index 6f528f9fd5c3..3a69087d5e5c 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalMember2MemberGroupDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalMember2MemberGroupDto.cs @@ -15,7 +15,7 @@ internal sealed class ExternalMember2MemberGroupDto public const string TableName = Constants.DatabaseSchema.Tables.ExternalMember2MemberGroup; public const string ExternalMemberColumnName = "externalMemberId"; - private const string MemberGroupColumnName = "memberGroupId"; + public const string MemberGroupColumnName = "memberGroupId"; /// /// Gets or sets the identifier of the external member. diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/Member2MemberGroupDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/Member2MemberGroupDto.cs index 190fc14ccd2b..4699bbf57e2f 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/Member2MemberGroupDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/Member2MemberGroupDto.cs @@ -12,7 +12,7 @@ internal sealed class Member2MemberGroupDto public const string TableName = Constants.DatabaseSchema.Tables.Member2MemberGroup; public const string MemberColumnName = "Member"; - private const string MemberGroupColumnName = "MemberGroup"; + public const string MemberGroupColumnName = "MemberGroup"; /// /// Gets or sets the unique identifier of the member. diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/MemberDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/MemberDto.cs index f6d6d9e33260..cc5b30214482 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/MemberDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/MemberDto.cs @@ -9,7 +9,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos; [ExplicitColumns] internal sealed class MemberDto { - private const string TableName = Constants.DatabaseSchema.Tables.Member; + public const string TableName = Constants.DatabaseSchema.Tables.Member; public const string PrimaryKeyColumnName = Constants.DatabaseSchema.Columns.NodeIdName; /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberFilterRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberFilterRepository.cs index e4ed8dd9de88..ffb382aaed4b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberFilterRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberFilterRepository.cs @@ -8,6 +8,7 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Dtos; +using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; @@ -35,6 +36,13 @@ internal sealed class MemberFilterRepository : IMemberFilterRepository _scopeAccessor.AmbientScope?.SqlContext ?? throw new NotSupportedException("Need to be executed in a scope."); + private ISqlSyntaxProvider SqlSyntax => + _scopeAccessor.AmbientScope?.SqlContext.SqlSyntax + ?? throw new NotSupportedException("Need to be executed in a scope."); + + private string QTab(string tableName) => SqlSyntax.GetQuotedTableName(tableName); + private string QCol(string columnName) => SqlSyntax.GetQuotedColumnName(columnName); + private string QName(string name) => SqlSyntax.GetQuotedName(name); /// public async Task> GetPagedByFilterAsync(MemberFilter filter, int skip, int take, Ordering ordering) { @@ -81,39 +89,42 @@ private Sql BuildContentMemberSql(MemberFilter filter) { Sql sql = SqlContext.Sql() .Append($@"SELECT - n.[uniqueId] AS [Key], - m.[Email], - m.[LoginName] AS [UserName], - n.[text] AS [Name], - m.[IsApproved], - m.[IsLockedOut], - m.[LastLoginDate], - m.[LastLockoutDate], - m.[LastPasswordChangeDate], - CAST(0 AS bit) AS [IsExternalOnly], - ctn.[uniqueId] AS [MemberTypeKey], - ctn.[text] AS [MemberTypeName], - ctd.[icon] AS [MemberTypeIcon] - FROM [{Constants.DatabaseSchema.Tables.Member}] m - INNER JOIN [{Constants.DatabaseSchema.Tables.Node}] n ON n.[id] = m.[nodeId] - INNER JOIN [{Constants.DatabaseSchema.Tables.Content}] ct ON ct.[nodeId] = n.[id] - INNER JOIN [{Constants.DatabaseSchema.Tables.Node}] ctn ON ctn.[id] = ct.[contentTypeId] - INNER JOIN [cmsContentType] ctd ON ctd.[nodeId] = ctn.[id]"); + n.{QCol(NodeDto.KeyColumnName)} AS {QName("key")}, + m.{QCol("Email")}, + m.{QCol("LoginName")} AS {QName("userName")}, + n.{QCol(NodeDto.TextColumnName)} AS {QName("name")}, + m.{QCol("isApproved")}, + m.{QCol("isLockedOut")}, + m.{QCol("lastLoginDate")}, + m.{QCol("lastLockoutDate")}, + m.{QCol("lastPasswordChangeDate")}, + CAST(0 AS bit) AS {QName("isExternalOnly")}, + ctn.{QCol("uniqueId")} AS {QName("memberTypeKey")}, + ctn.{QCol("text")} AS {QName("memberTypeName")}, + ctd.{QCol("icon")} AS {QName("memberTypeIcon")} + FROM {QTab(MemberDto.TableName)} m + INNER JOIN {QTab(NodeDto.TableName)} n ON n.{QCol(NodeDto.PrimaryKeyColumnName)} = m.{QCol(MemberDto.PrimaryKeyColumnName)} + INNER JOIN {QTab(ContentDto.TableName)} ct ON ct.{QCol(ContentDto.PrimaryKeyColumnName)} = n.{QCol(NodeDto.PrimaryKeyColumnName)} + INNER JOIN {QTab(NodeDto.TableName)} ctn ON ctn.{QCol(NodeDto.PrimaryKeyColumnName)} = ct.{QCol(ContentDto.ContentTypeIdColumnName)} + INNER JOIN {QTab(ContentTypeDto.TableName)} ctd ON ctd.{QCol(ContentTypeDto.NodeIdColumnName)} = ctn.{QCol(NodeDto.PrimaryKeyColumnName)}"); // Append optional JOINs before any WHERE clauses. if (filter.MemberGroupName.IsNullOrWhiteSpace() is false) { sql = sql.Append( - $@"INNER JOIN [{Constants.DatabaseSchema.Tables.Member2MemberGroup}] m2mg ON m2mg.[{Member2MemberGroupDto.MemberColumnName}] = m.[nodeId] - INNER JOIN [{Constants.DatabaseSchema.Tables.Node}] mgn ON mgn.[id] = m2mg.[MemberGroup] AND mgn.[text] = @groupName", new { groupName = filter.MemberGroupName }); + $@"INNER JOIN {QTab(Member2MemberGroupDto.TableName)} m2mg + ON m2mg.{QCol(Member2MemberGroupDto.MemberColumnName)} = m.{QCol(MemberDto.PrimaryKeyColumnName)} + INNER JOIN {QTab(NodeDto.TableName)} mgn + ON mgn.{QCol(NodeDto.PrimaryKeyColumnName)} = m2mg.{QCol(Member2MemberGroupDto.MemberGroupColumnName)} + AND mgn.{QCol(NodeDto.TextColumnName)} = @groupName", new { groupName = filter.MemberGroupName }); } if (filter.MemberTypeId.HasValue) { - sql = sql.Append("WHERE ctn.[uniqueId] = @typeId", new { typeId = filter.MemberTypeId.Value }); + sql = sql.Append($"WHERE ctn.{QCol("uniqueId")} = @typeId", new { typeId = filter.MemberTypeId.Value }); } - AppendWhereFilters(ref sql, filter, "m.[Email]", "m.[LoginName]", "n.[text]", "m.[IsApproved]", "m.[IsLockedOut]", filter.MemberTypeId.HasValue); + AppendWhereFilters(ref sql, filter, $"m.{QCol("Email")}", $"m.{QCol("LoginName")}", $"n.{QCol("text")}", $"m.{QCol("isApproved")}", $"m.{QCol("isLockedOut")}", filter.MemberTypeId.HasValue); return sql; } @@ -122,34 +133,37 @@ private Sql BuildExternalMemberSql(MemberFilter filter) { Sql sql = SqlContext.Sql() .Append($@"SELECT - em.[key] AS [Key], - em.[email] AS [Email], - em.[userName] AS [UserName], - em.[name] AS [Name], - em.[isApproved] AS [IsApproved], - em.[isLockedOut] AS [IsLockedOut], - em.[lastLoginDate] AS [LastLoginDate], - em.[lastLockoutDate] AS [LastLockoutDate], - CAST(NULL AS datetime) AS [LastPasswordChangeDate], - CAST(1 AS bit) AS [IsExternalOnly], - CAST(NULL AS uniqueidentifier) AS [MemberTypeKey], - CAST(NULL AS nvarchar(255)) AS [MemberTypeName], - CAST(NULL AS nvarchar(255)) AS [MemberTypeIcon] - FROM [{Constants.DatabaseSchema.Tables.ExternalMember}] em"); + em.{QCol("key")}, + em.{QCol("email")}, + em.{QCol("userName")}, + em.{QCol("name")}, + em.{QCol("isApproved")}, + em.{QCol("isLockedOut")}, + em.{QCol("lastLoginDate")}, + em.{QCol("lastLockoutDate")}, + NULL AS {QName("lastPasswordChangeDate")}, + CAST(1 AS bit) AS {QName("isExternalOnly")}, + NULL AS {QName("memberTypeKey")}, + NULL AS {QName("memberTypeName")}, + NULL AS {QName("memberTypeIcon")} + FROM {QTab(ExternalMemberDto.TableName)} em"); if (filter.MemberGroupName.IsNullOrWhiteSpace() is false) { sql = sql.Append( - $@"INNER JOIN [{Constants.DatabaseSchema.Tables.ExternalMember2MemberGroup}] em2mg ON em2mg.[externalMemberId] = em.[id] - INNER JOIN [{Constants.DatabaseSchema.Tables.Node}] emgn ON emgn.[id] = em2mg.[memberGroupId] AND emgn.[text] = @groupName", new { groupName = filter.MemberGroupName }); + $@"INNER JOIN {QTab(ExternalMember2MemberGroupDto.TableName)} em2mg + ON em2mg.{QCol(ExternalMember2MemberGroupDto.ExternalMemberColumnName)} = em.{QCol(ExternalMemberDto.PrimaryKeyColumnName)} + INNER JOIN {QTab(NodeDto.TableName)} emgn + ON emgn.{QCol(NodeDto.PrimaryKeyColumnName)} = em2mg.{QCol(ExternalMember2MemberGroupDto.MemberGroupColumnName)} + AND emgn.{QCol(NodeDto.TextColumnName)} = @groupName", new { groupName = filter.MemberGroupName }); } - AppendWhereFilters(ref sql, filter, "em.[email]", "em.[userName]", "em.[name]", "em.[isApproved]", "em.[isLockedOut]", hasWhereAlready: false); + AppendWhereFilters(ref sql, filter, $"em.{QCol("email")}", $"em.{QCol("userName")}", $"em.{QCol("name")}", $"em.{QCol("isApproved")}", $"em.{QCol("isLockedOut")}", hasWhereAlready: false); return sql; } - private static void AppendWhereFilters( + private void AppendWhereFilters( ref Sql sql, MemberFilter filter, string emailCol, @@ -186,15 +200,15 @@ private static void AppendWhereFilters( } } - private static string MapOrderByColumn(string? orderBy) => + private string MapOrderByColumn(string? orderBy) => orderBy?.ToLowerInvariant() switch { - "email" => "[Email]", - "name" => "[Name]", - "isapproved" => "[IsApproved]", - "islockedout" => "[IsLockedOut]", - "lastlogindate" => "[LastLoginDate]", - _ => "[UserName]", + "email" => QCol("email"), + "name" => QCol("name"), + "isapproved" => QCol("isApproved"), + "islockedout" => QCol("isLockedOut"), + "lastlogindate" => QCol("lastLoginDate"), + _ => QCol("userName"), }; private static MemberFilterItem MapToItem(MemberFilterItemDto dto) => @@ -222,43 +236,43 @@ private static MemberFilterItem MapToItem(MemberFilterItemDto dto) => [ExplicitColumns] private sealed class MemberFilterItemDto { - [Column("Key")] + [Column("key")] public Guid Key { get; set; } - [Column("Email")] + [Column("email")] public string Email { get; set; } = string.Empty; - [Column("UserName")] + [Column("userName")] public string UserName { get; set; } = string.Empty; - [Column("Name")] + [Column("name")] public string? Name { get; set; } - [Column("IsApproved")] + [Column("isApproved")] public bool IsApproved { get; set; } - [Column("IsLockedOut")] + [Column("isLockedOut")] public bool IsLockedOut { get; set; } - [Column("LastLoginDate")] + [Column("lastLoginDate")] public DateTime? LastLoginDate { get; set; } - [Column("LastLockoutDate")] + [Column("lastLockoutDate")] public DateTime? LastLockoutDate { get; set; } - [Column("LastPasswordChangeDate")] + [Column("lastPasswordChangeDate")] public DateTime? LastPasswordChangeDate { get; set; } - [Column("IsExternalOnly")] + [Column("isExternalOnly")] public bool IsExternalOnly { get; set; } - [Column("MemberTypeKey")] + [Column("memberTypeKey")] public Guid? MemberTypeKey { get; set; } - [Column("MemberTypeName")] + [Column("memberTypeName")] public string? MemberTypeName { get; set; } - [Column("MemberTypeIcon")] + [Column("memberTypeIcon")] public string? MemberTypeIcon { get; set; } } } From 687a9ab7db83dcbbc6d62eb5f4879a0383cd37db Mon Sep 17 00:00:00 2001 From: Dirk Seefeld Date: Sun, 26 Apr 2026 11:28:50 +0200 Subject: [PATCH 4/6] fix formating --- .../Persistence/Repositories/Implement/MemberFilterRepository.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberFilterRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberFilterRepository.cs index ffb382aaed4b..a0553b84044e 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberFilterRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberFilterRepository.cs @@ -43,6 +43,7 @@ internal sealed class MemberFilterRepository : IMemberFilterRepository private string QTab(string tableName) => SqlSyntax.GetQuotedTableName(tableName); private string QCol(string columnName) => SqlSyntax.GetQuotedColumnName(columnName); private string QName(string name) => SqlSyntax.GetQuotedName(name); + /// public async Task> GetPagedByFilterAsync(MemberFilter filter, int skip, int take, Ordering ordering) { From 916183a0fe3635b77107f480ace42071ba781346 Mon Sep 17 00:00:00 2001 From: Dirk Seefeld Date: Mon, 27 Apr 2026 10:45:43 +0200 Subject: [PATCH 5/6] restore MemberFilterRepository --- .../Dtos/ExternalMember2MemberGroupDto.cs | 2 +- .../Persistence/Dtos/Member2MemberGroupDto.cs | 2 +- .../Persistence/Dtos/MemberDto.cs | 2 +- .../Implement/MemberFilterRepository.cs | 135 ++++++++---------- 4 files changed, 63 insertions(+), 78 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalMember2MemberGroupDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalMember2MemberGroupDto.cs index 3a69087d5e5c..6f528f9fd5c3 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalMember2MemberGroupDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/ExternalMember2MemberGroupDto.cs @@ -15,7 +15,7 @@ internal sealed class ExternalMember2MemberGroupDto public const string TableName = Constants.DatabaseSchema.Tables.ExternalMember2MemberGroup; public const string ExternalMemberColumnName = "externalMemberId"; - public const string MemberGroupColumnName = "memberGroupId"; + private const string MemberGroupColumnName = "memberGroupId"; /// /// Gets or sets the identifier of the external member. diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/Member2MemberGroupDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/Member2MemberGroupDto.cs index 4699bbf57e2f..190fc14ccd2b 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/Member2MemberGroupDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/Member2MemberGroupDto.cs @@ -12,7 +12,7 @@ internal sealed class Member2MemberGroupDto public const string TableName = Constants.DatabaseSchema.Tables.Member2MemberGroup; public const string MemberColumnName = "Member"; - public const string MemberGroupColumnName = "MemberGroup"; + private const string MemberGroupColumnName = "MemberGroup"; /// /// Gets or sets the unique identifier of the member. diff --git a/src/Umbraco.Infrastructure/Persistence/Dtos/MemberDto.cs b/src/Umbraco.Infrastructure/Persistence/Dtos/MemberDto.cs index cc5b30214482..f6d6d9e33260 100644 --- a/src/Umbraco.Infrastructure/Persistence/Dtos/MemberDto.cs +++ b/src/Umbraco.Infrastructure/Persistence/Dtos/MemberDto.cs @@ -9,7 +9,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Dtos; [ExplicitColumns] internal sealed class MemberDto { - public const string TableName = Constants.DatabaseSchema.Tables.Member; + private const string TableName = Constants.DatabaseSchema.Tables.Member; public const string PrimaryKeyColumnName = Constants.DatabaseSchema.Columns.NodeIdName; /// diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberFilterRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberFilterRepository.cs index a0553b84044e..e4ed8dd9de88 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberFilterRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/MemberFilterRepository.cs @@ -8,7 +8,6 @@ using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.Persistence.Dtos; -using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; using Umbraco.Cms.Infrastructure.Scoping; using Umbraco.Extensions; @@ -36,14 +35,6 @@ internal sealed class MemberFilterRepository : IMemberFilterRepository _scopeAccessor.AmbientScope?.SqlContext ?? throw new NotSupportedException("Need to be executed in a scope."); - private ISqlSyntaxProvider SqlSyntax => - _scopeAccessor.AmbientScope?.SqlContext.SqlSyntax - ?? throw new NotSupportedException("Need to be executed in a scope."); - - private string QTab(string tableName) => SqlSyntax.GetQuotedTableName(tableName); - private string QCol(string columnName) => SqlSyntax.GetQuotedColumnName(columnName); - private string QName(string name) => SqlSyntax.GetQuotedName(name); - /// public async Task> GetPagedByFilterAsync(MemberFilter filter, int skip, int take, Ordering ordering) { @@ -90,42 +81,39 @@ private Sql BuildContentMemberSql(MemberFilter filter) { Sql sql = SqlContext.Sql() .Append($@"SELECT - n.{QCol(NodeDto.KeyColumnName)} AS {QName("key")}, - m.{QCol("Email")}, - m.{QCol("LoginName")} AS {QName("userName")}, - n.{QCol(NodeDto.TextColumnName)} AS {QName("name")}, - m.{QCol("isApproved")}, - m.{QCol("isLockedOut")}, - m.{QCol("lastLoginDate")}, - m.{QCol("lastLockoutDate")}, - m.{QCol("lastPasswordChangeDate")}, - CAST(0 AS bit) AS {QName("isExternalOnly")}, - ctn.{QCol("uniqueId")} AS {QName("memberTypeKey")}, - ctn.{QCol("text")} AS {QName("memberTypeName")}, - ctd.{QCol("icon")} AS {QName("memberTypeIcon")} - FROM {QTab(MemberDto.TableName)} m - INNER JOIN {QTab(NodeDto.TableName)} n ON n.{QCol(NodeDto.PrimaryKeyColumnName)} = m.{QCol(MemberDto.PrimaryKeyColumnName)} - INNER JOIN {QTab(ContentDto.TableName)} ct ON ct.{QCol(ContentDto.PrimaryKeyColumnName)} = n.{QCol(NodeDto.PrimaryKeyColumnName)} - INNER JOIN {QTab(NodeDto.TableName)} ctn ON ctn.{QCol(NodeDto.PrimaryKeyColumnName)} = ct.{QCol(ContentDto.ContentTypeIdColumnName)} - INNER JOIN {QTab(ContentTypeDto.TableName)} ctd ON ctd.{QCol(ContentTypeDto.NodeIdColumnName)} = ctn.{QCol(NodeDto.PrimaryKeyColumnName)}"); + n.[uniqueId] AS [Key], + m.[Email], + m.[LoginName] AS [UserName], + n.[text] AS [Name], + m.[IsApproved], + m.[IsLockedOut], + m.[LastLoginDate], + m.[LastLockoutDate], + m.[LastPasswordChangeDate], + CAST(0 AS bit) AS [IsExternalOnly], + ctn.[uniqueId] AS [MemberTypeKey], + ctn.[text] AS [MemberTypeName], + ctd.[icon] AS [MemberTypeIcon] + FROM [{Constants.DatabaseSchema.Tables.Member}] m + INNER JOIN [{Constants.DatabaseSchema.Tables.Node}] n ON n.[id] = m.[nodeId] + INNER JOIN [{Constants.DatabaseSchema.Tables.Content}] ct ON ct.[nodeId] = n.[id] + INNER JOIN [{Constants.DatabaseSchema.Tables.Node}] ctn ON ctn.[id] = ct.[contentTypeId] + INNER JOIN [cmsContentType] ctd ON ctd.[nodeId] = ctn.[id]"); // Append optional JOINs before any WHERE clauses. if (filter.MemberGroupName.IsNullOrWhiteSpace() is false) { sql = sql.Append( - $@"INNER JOIN {QTab(Member2MemberGroupDto.TableName)} m2mg - ON m2mg.{QCol(Member2MemberGroupDto.MemberColumnName)} = m.{QCol(MemberDto.PrimaryKeyColumnName)} - INNER JOIN {QTab(NodeDto.TableName)} mgn - ON mgn.{QCol(NodeDto.PrimaryKeyColumnName)} = m2mg.{QCol(Member2MemberGroupDto.MemberGroupColumnName)} - AND mgn.{QCol(NodeDto.TextColumnName)} = @groupName", new { groupName = filter.MemberGroupName }); + $@"INNER JOIN [{Constants.DatabaseSchema.Tables.Member2MemberGroup}] m2mg ON m2mg.[{Member2MemberGroupDto.MemberColumnName}] = m.[nodeId] + INNER JOIN [{Constants.DatabaseSchema.Tables.Node}] mgn ON mgn.[id] = m2mg.[MemberGroup] AND mgn.[text] = @groupName", new { groupName = filter.MemberGroupName }); } if (filter.MemberTypeId.HasValue) { - sql = sql.Append($"WHERE ctn.{QCol("uniqueId")} = @typeId", new { typeId = filter.MemberTypeId.Value }); + sql = sql.Append("WHERE ctn.[uniqueId] = @typeId", new { typeId = filter.MemberTypeId.Value }); } - AppendWhereFilters(ref sql, filter, $"m.{QCol("Email")}", $"m.{QCol("LoginName")}", $"n.{QCol("text")}", $"m.{QCol("isApproved")}", $"m.{QCol("isLockedOut")}", filter.MemberTypeId.HasValue); + AppendWhereFilters(ref sql, filter, "m.[Email]", "m.[LoginName]", "n.[text]", "m.[IsApproved]", "m.[IsLockedOut]", filter.MemberTypeId.HasValue); return sql; } @@ -134,37 +122,34 @@ private Sql BuildExternalMemberSql(MemberFilter filter) { Sql sql = SqlContext.Sql() .Append($@"SELECT - em.{QCol("key")}, - em.{QCol("email")}, - em.{QCol("userName")}, - em.{QCol("name")}, - em.{QCol("isApproved")}, - em.{QCol("isLockedOut")}, - em.{QCol("lastLoginDate")}, - em.{QCol("lastLockoutDate")}, - NULL AS {QName("lastPasswordChangeDate")}, - CAST(1 AS bit) AS {QName("isExternalOnly")}, - NULL AS {QName("memberTypeKey")}, - NULL AS {QName("memberTypeName")}, - NULL AS {QName("memberTypeIcon")} - FROM {QTab(ExternalMemberDto.TableName)} em"); + em.[key] AS [Key], + em.[email] AS [Email], + em.[userName] AS [UserName], + em.[name] AS [Name], + em.[isApproved] AS [IsApproved], + em.[isLockedOut] AS [IsLockedOut], + em.[lastLoginDate] AS [LastLoginDate], + em.[lastLockoutDate] AS [LastLockoutDate], + CAST(NULL AS datetime) AS [LastPasswordChangeDate], + CAST(1 AS bit) AS [IsExternalOnly], + CAST(NULL AS uniqueidentifier) AS [MemberTypeKey], + CAST(NULL AS nvarchar(255)) AS [MemberTypeName], + CAST(NULL AS nvarchar(255)) AS [MemberTypeIcon] + FROM [{Constants.DatabaseSchema.Tables.ExternalMember}] em"); if (filter.MemberGroupName.IsNullOrWhiteSpace() is false) { sql = sql.Append( - $@"INNER JOIN {QTab(ExternalMember2MemberGroupDto.TableName)} em2mg - ON em2mg.{QCol(ExternalMember2MemberGroupDto.ExternalMemberColumnName)} = em.{QCol(ExternalMemberDto.PrimaryKeyColumnName)} - INNER JOIN {QTab(NodeDto.TableName)} emgn - ON emgn.{QCol(NodeDto.PrimaryKeyColumnName)} = em2mg.{QCol(ExternalMember2MemberGroupDto.MemberGroupColumnName)} - AND emgn.{QCol(NodeDto.TextColumnName)} = @groupName", new { groupName = filter.MemberGroupName }); + $@"INNER JOIN [{Constants.DatabaseSchema.Tables.ExternalMember2MemberGroup}] em2mg ON em2mg.[externalMemberId] = em.[id] + INNER JOIN [{Constants.DatabaseSchema.Tables.Node}] emgn ON emgn.[id] = em2mg.[memberGroupId] AND emgn.[text] = @groupName", new { groupName = filter.MemberGroupName }); } - AppendWhereFilters(ref sql, filter, $"em.{QCol("email")}", $"em.{QCol("userName")}", $"em.{QCol("name")}", $"em.{QCol("isApproved")}", $"em.{QCol("isLockedOut")}", hasWhereAlready: false); + AppendWhereFilters(ref sql, filter, "em.[email]", "em.[userName]", "em.[name]", "em.[isApproved]", "em.[isLockedOut]", hasWhereAlready: false); return sql; } - private void AppendWhereFilters( + private static void AppendWhereFilters( ref Sql sql, MemberFilter filter, string emailCol, @@ -201,15 +186,15 @@ private void AppendWhereFilters( } } - private string MapOrderByColumn(string? orderBy) => + private static string MapOrderByColumn(string? orderBy) => orderBy?.ToLowerInvariant() switch { - "email" => QCol("email"), - "name" => QCol("name"), - "isapproved" => QCol("isApproved"), - "islockedout" => QCol("isLockedOut"), - "lastlogindate" => QCol("lastLoginDate"), - _ => QCol("userName"), + "email" => "[Email]", + "name" => "[Name]", + "isapproved" => "[IsApproved]", + "islockedout" => "[IsLockedOut]", + "lastlogindate" => "[LastLoginDate]", + _ => "[UserName]", }; private static MemberFilterItem MapToItem(MemberFilterItemDto dto) => @@ -237,43 +222,43 @@ private static MemberFilterItem MapToItem(MemberFilterItemDto dto) => [ExplicitColumns] private sealed class MemberFilterItemDto { - [Column("key")] + [Column("Key")] public Guid Key { get; set; } - [Column("email")] + [Column("Email")] public string Email { get; set; } = string.Empty; - [Column("userName")] + [Column("UserName")] public string UserName { get; set; } = string.Empty; - [Column("name")] + [Column("Name")] public string? Name { get; set; } - [Column("isApproved")] + [Column("IsApproved")] public bool IsApproved { get; set; } - [Column("isLockedOut")] + [Column("IsLockedOut")] public bool IsLockedOut { get; set; } - [Column("lastLoginDate")] + [Column("LastLoginDate")] public DateTime? LastLoginDate { get; set; } - [Column("lastLockoutDate")] + [Column("LastLockoutDate")] public DateTime? LastLockoutDate { get; set; } - [Column("lastPasswordChangeDate")] + [Column("LastPasswordChangeDate")] public DateTime? LastPasswordChangeDate { get; set; } - [Column("isExternalOnly")] + [Column("IsExternalOnly")] public bool IsExternalOnly { get; set; } - [Column("memberTypeKey")] + [Column("MemberTypeKey")] public Guid? MemberTypeKey { get; set; } - [Column("memberTypeName")] + [Column("MemberTypeName")] public string? MemberTypeName { get; set; } - [Column("memberTypeIcon")] + [Column("MemberTypeIcon")] public string? MemberTypeIcon { get; set; } } } From 9de48429d1973e6bb1a697a1f363d32b4949f832 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Tue, 28 Apr 2026 10:08:49 +0200 Subject: [PATCH 6/6] Correct usage of field name constant. --- .../Upgrade/V_17_4_0/FixLabelDataTypeDbTypeFromConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_4_0/FixLabelDataTypeDbTypeFromConfiguration.cs b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_4_0/FixLabelDataTypeDbTypeFromConfiguration.cs index 9523da436355..18e59a58307d 100644 --- a/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_4_0/FixLabelDataTypeDbTypeFromConfiguration.cs +++ b/src/Umbraco.Infrastructure/Migrations/Upgrade/V_17_4_0/FixLabelDataTypeDbTypeFromConfiguration.cs @@ -74,7 +74,7 @@ internal static async Task ExecuteMigration(IUmbracoDatabase database, IDataType UPDATE {syntax.GetQuotedTableName(PropertyDataDto.TableName)} SET {syntax.GetQuotedColumnName(PropertyDataDto.TextValueColumnName)} = {syntax.GetQuotedColumnName(PropertyDataDto.VarcharValueColumnName)}, {syntax.GetQuotedColumnName(PropertyDataDto.VarcharValueColumnName)} = NULL WHERE {syntax.GetQuotedColumnName(PropertyDataDto.PropertyTypeIdColumnName)} IN ( - SELECT {syntax.GetQuotedColumnName(PropertyDataDto.PrimaryKeyColumnName)} + SELECT {syntax.GetQuotedColumnName(PropertyTypeDto.PrimaryKeyColumnName)} FROM {syntax.GetQuotedTableName(PropertyTypeDto.TableName)} WHERE {syntax.GetQuotedColumnName(PropertyTypeDto.DataTypeIdColumnName)} IN ( SELECT {syntax.GetQuotedColumnName(DataTypeDto.PrimaryKeyColumnName)}