Skip to content

Commit c61eaa2

Browse files
author
Paul Johnson
authored
Merge pull request #11369 from umbraco/v8/bugfix/maxparametercount
Add Constants.Sql.MaxParameterCount and update query logic
2 parents 3fb150d + 60be71d commit c61eaa2

16 files changed

+79
-62
lines changed

src/Umbraco.Core/Constants-Sql.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Umbraco.Core
2+
{
3+
public static partial class Constants
4+
{
5+
public static class Sql
6+
{
7+
/// <summary>
8+
/// The maximum amount of parameters that can be used in a query.
9+
/// </summary>
10+
/// <remarks>
11+
/// The actual limit is 2100 (https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server),
12+
/// but we want to ensure there's room for additional parameters if this value is used to create groups/batches.
13+
/// </remarks>
14+
public const int MaxParameterCount = 2000;
15+
}
16+
}
17+
}

src/Umbraco.Core/Persistence/NPocoDatabaseExtensions-Bulk.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ internal static IDbCommand[] GenerateBulkInsertCommands<T>(this IUmbracoDatabase
137137
// Math.Floor(2100 / 8) = 262 record per command
138138
// 4168 / 262 = 15.908... = there will be 16 command in total
139139
// (if we have disabled db parameters, then all records will be included, in only one command)
140-
var recordsPerCommand = paramsPerRecord == 0 ? int.MaxValue : Convert.ToInt32(Math.Floor(2000.00 / paramsPerRecord));
140+
var recordsPerCommand = paramsPerRecord == 0 ? int.MaxValue : Convert.ToInt32(Math.Floor((double)Constants.Sql.MaxParameterCount / paramsPerRecord));
141141
var commandsCount = Convert.ToInt32(Math.Ceiling((double)records.Length / recordsPerCommand));
142142

143143
var commands = new IDbCommand[commandsCount];

src/Umbraco.Core/Persistence/Repositories/Implement/AuditEntryRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ protected override IEnumerable<IAuditEntry> PerformGetAll(params int[] ids)
5454

5555
var entries = new List<IAuditEntry>();
5656

57-
foreach (var group in ids.InGroupsOf(2000))
57+
foreach (var group in ids.InGroupsOf(Constants.Sql.MaxParameterCount))
5858
{
5959
var sql = Sql()
6060
.Select<AuditEntryDto>()

src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ protected IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<Te
638638
// in the table?
639639

640640
// get all PropertyDataDto for all definitions / versions
641-
var allPropertyDataDtos = Database.FetchByGroups<PropertyDataDto, int>(versions, 2000, batch =>
641+
var allPropertyDataDtos = Database.FetchByGroups<PropertyDataDto, int>(versions, Constants.Sql.MaxParameterCount, batch =>
642642
SqlContext.Sql()
643643
.Select<PropertyDataDto>()
644644
.From<PropertyDataDto>()
@@ -647,7 +647,7 @@ protected IDictionary<int, PropertyCollection> GetPropertyCollections<T>(List<Te
647647

648648
// get PropertyDataDto distinct PropertyTypeDto
649649
var allPropertyTypeIds = allPropertyDataDtos.Select(x => x.PropertyTypeId).Distinct().ToList();
650-
var allPropertyTypeDtos = Database.FetchByGroups<PropertyTypeDto, int>(allPropertyTypeIds, 2000, batch =>
650+
var allPropertyTypeDtos = Database.FetchByGroups<PropertyTypeDto, int>(allPropertyTypeIds, Constants.Sql.MaxParameterCount, batch =>
651651
SqlContext.Sql()
652652
.Select<PropertyTypeDto>(r => r.Select(x => x.DataTypeDto))
653653
.From<PropertyTypeDto>()

src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,7 @@ private void CopyTagData(int? sourceLanguageId, int? targetLanguageId, IReadOnly
768768
// note: important to use SqlNullableEquals for nullable types, cannot directly compare language identifiers
769769

770770
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
771-
if (whereInArgsCount > 2000)
771+
if (whereInArgsCount > Constants.Sql.MaxParameterCount)
772772
throw new NotSupportedException("Too many property/content types.");
773773

774774
// delete existing relations (for target language)
@@ -906,7 +906,7 @@ private void CopyPropertyData(int? sourceLanguageId, int? targetLanguageId, IRea
906906
// note: important to use SqlNullableEquals for nullable types, cannot directly compare language identifiers
907907
//
908908
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
909-
if (whereInArgsCount > 2000)
909+
if (whereInArgsCount > Constants.Sql.MaxParameterCount)
910910
throw new NotSupportedException("Too many property/content types.");
911911

912912
//first clear out any existing property data that might already exists under the target language
@@ -1005,7 +1005,7 @@ private void RenormalizeDocumentEditedFlags(IReadOnlyCollection<int> propertyTyp
10051005
//based on the current variance of each item to see if it's 'edited' value should be true/false.
10061006

10071007
var whereInArgsCount = propertyTypeIds.Count + (contentTypeIds?.Count ?? 0);
1008-
if (whereInArgsCount > 2000)
1008+
if (whereInArgsCount > Constants.Sql.MaxParameterCount)
10091009
throw new NotSupportedException("Too many property/content types.");
10101010

10111011
var propertySql = Sql()
@@ -1094,14 +1094,20 @@ private void RenormalizeDocumentEditedFlags(IReadOnlyCollection<int> propertyTyp
10941094
}
10951095
}
10961096

1097-
//lookup all matching rows in umbracoDocumentCultureVariation
1098-
var docCultureVariationsToUpdate = editedLanguageVersions.InGroupsOf(2000)
1099-
.SelectMany(_ => Database.Fetch<DocumentCultureVariationDto>(
1100-
Sql().Select<DocumentCultureVariationDto>().From<DocumentCultureVariationDto>()
1101-
.WhereIn<DocumentCultureVariationDto>(x => x.LanguageId, editedLanguageVersions.Keys.Select(x => x.langId).ToList())
1102-
.WhereIn<DocumentCultureVariationDto>(x => x.NodeId, editedLanguageVersions.Keys.Select(x => x.nodeId))))
1103-
//convert to dictionary with the same key type
1104-
.ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x);
1097+
// lookup all matching rows in umbracoDocumentCultureVariation
1098+
// fetch in batches to account for maximum parameter count (distinct languages can't exceed 2000)
1099+
var languageIds = editedLanguageVersions.Keys.Select(x => x.langId).Distinct().ToArray();
1100+
var nodeIds = editedLanguageVersions.Keys.Select(x => x.nodeId).Distinct();
1101+
var docCultureVariationsToUpdate = nodeIds.InGroupsOf(Constants.Sql.MaxParameterCount - languageIds.Length)
1102+
.SelectMany(group =>
1103+
{
1104+
var sql = Sql().Select<DocumentCultureVariationDto>().From<DocumentCultureVariationDto>()
1105+
.WhereIn<DocumentCultureVariationDto>(x => x.LanguageId, languageIds)
1106+
.WhereIn<DocumentCultureVariationDto>(x => x.NodeId, group);
1107+
1108+
return Database.Fetch<DocumentCultureVariationDto>(sql);
1109+
})
1110+
.ToDictionary(x => (x.NodeId, (int?)x.LanguageId), x => x); //convert to dictionary with the same key type
11051111

11061112
var toUpdate = new List<DocumentCultureVariationDto>();
11071113
foreach (var ev in editedLanguageVersions)

src/Umbraco.Core/Persistence/Repositories/Implement/DictionaryRepository.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,13 +260,12 @@ public IEnumerable<IDictionaryItem> GetDictionaryItemDescendants(Guid? parentId)
260260

261261
Func<Guid[], IEnumerable<IEnumerable<IDictionaryItem>>> getItemsFromParents = guids =>
262262
{
263-
//needs to be in groups of 2000 because we are doing an IN clause and there's a max parameter count that can be used.
264-
return guids.InGroupsOf(2000)
265-
.Select(@group =>
263+
return guids.InGroupsOf(Constants.Sql.MaxParameterCount)
264+
.Select(group =>
266265
{
267266
var sqlClause = GetBaseQuery(false)
268267
.Where<DictionaryDto>(x => x.Parent != null)
269-
.Where($"{SqlSyntax.GetQuotedColumnName("parent")} IN (@parentIds)", new { parentIds = @group });
268+
.WhereIn<DictionaryDto>(x => x.Parent, group);
270269

271270
var translator = new SqlTranslator<IDictionaryItem>(sqlClause, Query<IDictionaryItem>());
272271
var sql = translator.Translate();

src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,7 +1342,7 @@ private IDictionary<int, ContentScheduleCollection> GetContentSchedule(params in
13421342
{
13431343
var result = new Dictionary<int, ContentScheduleCollection>();
13441344

1345-
var scheduleDtos = Database.FetchByGroups<ContentScheduleDto, int>(contentIds, 2000, batch => Sql()
1345+
var scheduleDtos = Database.FetchByGroups<ContentScheduleDto, int>(contentIds, Constants.Sql.MaxParameterCount, batch => Sql()
13461346
.Select<ContentScheduleDto>()
13471347
.From<ContentScheduleDto>()
13481348
.WhereIn<ContentScheduleDto>(x => x.NodeId, batch));
@@ -1391,7 +1391,7 @@ private IDictionary<int, List<ContentVariation>> GetContentVariations<T>(List<Te
13911391
}
13921392
if (versions.Count == 0) return new Dictionary<int, List<ContentVariation>>();
13931393

1394-
var dtos = Database.FetchByGroups<ContentVersionCultureVariationDto, int>(versions, 2000, batch
1394+
var dtos = Database.FetchByGroups<ContentVersionCultureVariationDto, int>(versions, Constants.Sql.MaxParameterCount, batch
13951395
=> Sql()
13961396
.Select<ContentVersionCultureVariationDto>()
13971397
.From<ContentVersionCultureVariationDto>()
@@ -1420,7 +1420,7 @@ private IDictionary<int, List<DocumentVariation>> GetDocumentVariations<T>(List<
14201420
{
14211421
var ids = temps.Select(x => x.Id);
14221422

1423-
var dtos = Database.FetchByGroups<DocumentCultureVariationDto, int>(ids, 2000, batch =>
1423+
var dtos = Database.FetchByGroups<DocumentCultureVariationDto, int>(ids, Constants.Sql.MaxParameterCount, batch =>
14241424
Sql()
14251425
.Select<DocumentCultureVariationDto>()
14261426
.From<DocumentCultureVariationDto>()

src/Umbraco.Core/Persistence/Repositories/Implement/EntityContainerRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ protected override IEnumerable<EntityContainer> PerformGetAll(params int[] ids)
6060
{
6161
if (ids.Any())
6262
{
63-
return Database.FetchByGroups<NodeDto, int>(ids, 2000, batch =>
63+
return Database.FetchByGroups<NodeDto, int>(ids, Constants.Sql.MaxParameterCount, batch =>
6464
GetBaseQuery(false)
6565
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
6666
.WhereIn<NodeDto>(x => x.NodeId, batch))

src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ private IEnumerable<DocumentEntitySlim> BuildVariants(IEnumerable<DocumentEntity
281281
if (v == null) return entitiesList;
282282

283283
// fetch all variant info dtos
284-
var dtos = Database.FetchByGroups<VariantInfoDto, int>(v.Select(x => x.Id), 2000, GetVariantInfos);
284+
var dtos = Database.FetchByGroups<VariantInfoDto, int>(v.Select(x => x.Id), Constants.Sql.MaxParameterCount, GetVariantInfos);
285285

286286
// group by node id (each group contains all languages)
287287
var xdtos = dtos.GroupBy(x => x.NodeId).ToDictionary(x => x.Key, x => x);

src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -431,16 +431,13 @@ public IEnumerable<IMember> FindMembersInRole(string roleName, string usernameTo
431431
var matchedMembers = Get(query).ToArray();
432432

433433
var membersInGroup = new List<IMember>();
434+
434435
//then we need to filter the matched members that are in the role
435-
//since the max sql params are 2100 on sql server, we'll reduce that to be safe for potentially other servers and run the queries in batches
436-
var inGroups = matchedMembers.InGroupsOf(1000);
437-
foreach (var batch in inGroups)
436+
foreach (var group in matchedMembers.Select(x => x.Id).InGroupsOf(Constants.Sql.MaxParameterCount))
438437
{
439-
var memberIdBatch = batch.Select(x => x.Id);
440-
441438
var sql = Sql().SelectAll().From<Member2MemberGroupDto>()
442439
.Where<Member2MemberGroupDto>(dto => dto.MemberGroup == memberGroup.Id)
443-
.WhereIn<Member2MemberGroupDto>(dto => dto.Member, memberIdBatch);
440+
.WhereIn<Member2MemberGroupDto>(dto => dto.Member, group);
444441

445442
var memberIdsInGroup = Database.Fetch<Member2MemberGroupDto>(sql)
446443
.Select(x => x.Member).ToArray();

0 commit comments

Comments
 (0)