From 71382faea66c0c6fefb7e0fe4ad5b7c813111a5a Mon Sep 17 00:00:00 2001 From: James Kovacs Date: Mon, 27 May 2024 16:46:59 -0600 Subject: [PATCH] CSHARP-2187: Flatten CombinedProjectionDefinitions to reduce recursive calls to Render. --- .../ProjectionDefinitionBuilder.cs | 13 ++++++++++++- .../ProjectionDefinitionBuilderTests.cs | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs b/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs index 5fc7d5c4cf3..34971cd803d 100644 --- a/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs +++ b/src/MongoDB.Driver/ProjectionDefinitionBuilder.cs @@ -789,7 +789,18 @@ internal sealed class CombinedProjectionDefinition : ProjectionDefiniti public CombinedProjectionDefinition(IEnumerable> projections) { - _projections = Ensure.IsNotNull(projections, nameof(projections)).ToList(); + // Unwind CombinedProjectionDefinitions to avoid deep recursion on Render + _projections = Ensure.IsNotNull(projections, nameof(projections)) + .Aggregate(new List>(), (current, projection) => + { + if (projection is CombinedProjectionDefinition combinedProjection) + { + current.AddRange(combinedProjection._projections); + } else + current.Add(projection); + return current; + }) + .ToList(); } public override BsonDocument Render(IBsonSerializer sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider) diff --git a/tests/MongoDB.Driver.Tests/ProjectionDefinitionBuilderTests.cs b/tests/MongoDB.Driver.Tests/ProjectionDefinitionBuilderTests.cs index 5f75bb8f8af..f3320e3723c 100644 --- a/tests/MongoDB.Driver.Tests/ProjectionDefinitionBuilderTests.cs +++ b/tests/MongoDB.Driver.Tests/ProjectionDefinitionBuilderTests.cs @@ -14,6 +14,7 @@ */ using System.Linq; +using System.Text; using FluentAssertions; using MongoDB.Bson; using MongoDB.Bson.Serialization; @@ -61,6 +62,24 @@ public void Combine_with_redundant_fields_using_extension_method() Assert(projection, "{LastName: 0, fn: 1}"); } + [Fact] + public void Combine_many_fields_should_not_overflow_stack_on_Render() + { + var subject = CreateSubject(); + + var projection = subject.Include(x => x.FirstName); + var expectedProjection = new StringBuilder("{fn: 1"); + for (int i = 0; i < 10000; i++) + { + var field = $"Field{i}"; + projection = projection.Include(field); + expectedProjection.Append($", {field}: 1"); + } + expectedProjection.Append("}"); + + Assert(projection, expectedProjection.ToString()); + } + [Fact] public void ElemMatch() {