From 1196d94adcc9acd54bd7cf30c0202548dbaa6d24 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 26 May 2026 16:08:49 +0200 Subject: [PATCH 1/2] CSHARP-6043: NullReferenceException in IEnumerableSerializerBase.Serialize when projecting nullable array with conditional Select --- .../Serializers/IEnumerableSerializerBase.cs | 11 +++ .../IEnumerableSerializerBaseTests.cs | 32 +++++++++ ...nToAggregationExpressionTranslatorTests.cs | 72 +++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConditionalExpressionToAggregationExpressionTranslatorTests.cs diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/IEnumerableSerializerBase.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/IEnumerableSerializerBase.cs index 28e29f70e41..e1ce8fb018e 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/IEnumerableSerializerBase.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/IEnumerableSerializerBase.cs @@ -14,6 +14,7 @@ */ using System.Collections.Generic; +using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Core.Misc; @@ -36,6 +37,11 @@ public IEnumerableSerializerBase(IBsonSerializer itemSerializer) public override TEnumerable Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { var reader = context.Reader; + if (reader.GetCurrentBsonType() == BsonType.Null) + { + reader.ReadNull(); + return default; + } reader.ReadStartArray(); var items = new List(); while (reader.ReadBsonType() != 0) @@ -64,6 +70,11 @@ obj is IEnumerableSerializerBase other && public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TEnumerable value) { var writer = context.Writer; + if (value == null) + { + writer.WriteNull(); + return; + } writer.WriteStartArray(); foreach (var item in value) { diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Serializers/IEnumerableSerializerBaseTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Serializers/IEnumerableSerializerBaseTests.cs index feeb8d4e689..a96a999f1b8 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Serializers/IEnumerableSerializerBaseTests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Serializers/IEnumerableSerializerBaseTests.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using FluentAssertions; using MongoDB.Bson; +using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Linq.Linq3Implementation.Serializers; @@ -101,6 +102,37 @@ public void GetHashCode_should_return_zero() result.Should().Be(0); } + [Fact] + public void Serialize_null_should_write_null() + { + var serializer = new ConcreteIEnumerableSerializerBase, int>(Int32Serializer.Instance); + var document = new BsonDocument(); + using var writer = new BsonDocumentWriter(document); + var context = BsonSerializationContext.CreateRoot(writer); + writer.WriteStartDocument(); + writer.WriteName("x"); + + serializer.Serialize(context, new BsonSerializationArgs(), null); + + writer.WriteEndDocument(); + document["x"].Should().Be(BsonNull.Value); + } + + [Fact] + public void Deserialize_null_should_return_default() + { + var serializer = new ConcreteIEnumerableSerializerBase, int>(Int32Serializer.Instance); + var document = new BsonDocument("x", BsonNull.Value); + using var reader = new BsonDocumentReader(document); + reader.ReadStartDocument(); + reader.ReadName("x"); + var context = BsonDeserializationContext.CreateRoot(reader); + + var result = serializer.Deserialize(context, new BsonDeserializationArgs { NominalType = typeof(IEnumerable) }); + + result.Should().BeNull(); + } + internal class ConcreteIEnumerableSerializerBase : IEnumerableSerializerBase where TEnumerable : IEnumerable { diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConditionalExpressionToAggregationExpressionTranslatorTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConditionalExpressionToAggregationExpressionTranslatorTests.cs new file mode 100644 index 00000000000..dd402cd4a54 --- /dev/null +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConditionalExpressionToAggregationExpressionTranslatorTests.cs @@ -0,0 +1,72 @@ +/* Copyright 2010-present MongoDB Inc. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using MongoDB.Driver.TestHelpers; +using Xunit; + +namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators +{ + public class ConditionalExpressionToAggregationExpressionTranslatorTests : LinqIntegrationTest + { + public ConditionalExpressionToAggregationExpressionTranslatorTests(ClassFixture fixture) + : base(fixture) + { + } + + [Fact] + public void Conditional_with_null_array_branch_and_Select_on_non_null_branch_should_work() + { + var collection = Fixture.Collection; + + var queryable = collection.AsQueryable() + .Select(x => x.Items == null ? null : x.Items.Select(a => a.Name)); + + var stages = Translate(collection, queryable); + AssertStages( + stages, + "{ $project : { _v : { $cond : { if : { $eq : ['$Items', null] }, then : null, else : '$Items.Name' } }, _id : 0 } }"); + + var results = queryable.ToList(); + results.Should().HaveCount(3); + results[0].Should().BeNull(); + results[1].Should().BeEmpty(); + results[2].Should().Equal("a", "b"); + } + + public class C + { + public int Id { get; set; } + public List Items { get; set; } + } + + public class Item + { + public string Name { get; set; } + } + + public sealed class ClassFixture : MongoCollectionFixture + { + protected override IEnumerable InitialData => + [ + new C { Id = 1, Items = null }, + new C { Id = 2, Items = [] }, + new C { Id = 3, Items = [new Item { Name = "a" }, new Item { Name = "b" }] } + ]; + } + } +} From d9415bce60fc6a21d2cbf47b7bd913229c8be723 Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Tue, 26 May 2026 19:12:33 +0200 Subject: [PATCH 2/2] Removed test. --- ...nToAggregationExpressionTranslatorTests.cs | 72 ------------------- 1 file changed, 72 deletions(-) delete mode 100644 tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConditionalExpressionToAggregationExpressionTranslatorTests.cs diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConditionalExpressionToAggregationExpressionTranslatorTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConditionalExpressionToAggregationExpressionTranslatorTests.cs deleted file mode 100644 index dd402cd4a54..00000000000 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/ConditionalExpressionToAggregationExpressionTranslatorTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -/* Copyright 2010-present MongoDB Inc. -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -using System.Collections.Generic; -using System.Linq; -using FluentAssertions; -using MongoDB.Driver.TestHelpers; -using Xunit; - -namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators -{ - public class ConditionalExpressionToAggregationExpressionTranslatorTests : LinqIntegrationTest - { - public ConditionalExpressionToAggregationExpressionTranslatorTests(ClassFixture fixture) - : base(fixture) - { - } - - [Fact] - public void Conditional_with_null_array_branch_and_Select_on_non_null_branch_should_work() - { - var collection = Fixture.Collection; - - var queryable = collection.AsQueryable() - .Select(x => x.Items == null ? null : x.Items.Select(a => a.Name)); - - var stages = Translate(collection, queryable); - AssertStages( - stages, - "{ $project : { _v : { $cond : { if : { $eq : ['$Items', null] }, then : null, else : '$Items.Name' } }, _id : 0 } }"); - - var results = queryable.ToList(); - results.Should().HaveCount(3); - results[0].Should().BeNull(); - results[1].Should().BeEmpty(); - results[2].Should().Equal("a", "b"); - } - - public class C - { - public int Id { get; set; } - public List Items { get; set; } - } - - public class Item - { - public string Name { get; set; } - } - - public sealed class ClassFixture : MongoCollectionFixture - { - protected override IEnumerable InitialData => - [ - new C { Id = 1, Items = null }, - new C { Id = 2, Items = [] }, - new C { Id = 3, Items = [new Item { Name = "a" }, new Item { Name = "b" }] } - ]; - } - } -}