From 13a6fc81106de94258d1f3f5e127a5c84ae51919 Mon Sep 17 00:00:00 2001 From: Kyra Ramesh Krishna Date: Fri, 1 May 2026 14:50:58 -0400 Subject: [PATCH 1/5] try to convert value type to match field type before serialization --- .../MethodTranslators/EqualsMethodToFilterTranslator.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslator.cs index ec3271f443f..4629fff0608 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslator.cs @@ -13,6 +13,7 @@ * limitations under the License. */ +using System; using System.Linq.Expressions; using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters; using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods; @@ -72,6 +73,14 @@ private static AstFilter Translate(TranslationContext context, Expression expres var fieldTranslation = ExpressionToFilterFieldTranslator.Translate(context, fieldExpression); var value = valueExpression.GetConstantValue(containingExpression: expression); + + var serializerValueType = fieldTranslation.Serializer.ValueType; + if (value != null && !serializerValueType.IsInstanceOfType(value)) + { + var targetType = Nullable.GetUnderlyingType(serializerValueType) ?? serializerValueType; + value = Convert.ChangeType(value, targetType); + } + var serializedValue = SerializationHelper.SerializeValue(fieldTranslation.Serializer, value); return AstFilter.Eq(fieldTranslation.Ast, serializedValue); } From f906cd674ed3531763f6c02c77efd72edfe9ab68 Mon Sep 17 00:00:00 2001 From: Kyra Ramesh Krishna Date: Fri, 1 May 2026 15:25:55 -0400 Subject: [PATCH 2/5] add test for equals --- .../EqualsMethodToFilterTranslatorTests.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs new file mode 100644 index 00000000000..428abdcefc4 --- /dev/null +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs @@ -0,0 +1,52 @@ +/* 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.Linq; +using FluentAssertions; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using Xunit; + +namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.MethodTranslators; + +public class EqualsMethodToFilterTranslatorTests +{ + [Fact] + public void Equal_should_not_throw_when_comparing_uint64_and_int32() + { + var client = + new MongoClient("mongodb://localhost:27017"); // todo kyra q: is there a reason for this specific number? + var database = client.GetDatabase("test"); + database.DropCollection("employees"); + + var collection = database.GetCollection("employees"); + collection.InsertOne(new Employee { EmployeeID = 1, ReportsTo = 2 }); + + ulong longPrm = 2; + + var results = collection.AsQueryable() + .Where(e => e.ReportsTo.Equals(longPrm)) + .ToList(); + + results.Should().HaveCount(1); + } + + public class Employee + { + [BsonId] + public int EmployeeID { get; set; } + public int? ReportsTo { get; set; } + } +} From 4d73a9f5a1feed2767f46056eb821a43992a299e Mon Sep 17 00:00:00 2001 From: Kyra Ramesh Krishna Date: Fri, 1 May 2026 15:33:21 -0400 Subject: [PATCH 3/5] make testing better --- .../EqualsMethodToFilterTranslatorTests.cs | 108 ++++++++++++++---- 1 file changed, 84 insertions(+), 24 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs index 428abdcefc4..bcb8dd50f60 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs @@ -13,40 +13,100 @@ * limitations under the License. */ +using System.Collections.Generic; using System.Linq; using FluentAssertions; -using MongoDB.Bson; -using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Driver.TestHelpers; using Xunit; -namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.MethodTranslators; - -public class EqualsMethodToFilterTranslatorTests +namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.MethodTranslators { - [Fact] - public void Equal_should_not_throw_when_comparing_uint64_and_int32() + public class EqualsMethodToFilterTranslatorTests : LinqIntegrationTest { - var client = - new MongoClient("mongodb://localhost:27017"); // todo kyra q: is there a reason for this specific number? - var database = client.GetDatabase("test"); - database.DropCollection("employees"); + public EqualsMethodToFilterTranslatorTests(ClassFixture fixture) : base(fixture) + { + } - var collection = database.GetCollection("employees"); - collection.InsertOne(new Employee { EmployeeID = 1, ReportsTo = 2 }); + [Fact] + public void Equals_with_uint64_and_nullable_int32_should_translate() + { + var collection = Fixture.Collection; + ulong longPrm = 2; - ulong longPrm = 2; + var queryable = collection.AsQueryable() + .Where(e => e.ReportsTo.Equals(longPrm)); - var results = collection.AsQueryable() - .Where(e => e.ReportsTo.Equals(longPrm)) - .ToList(); + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match : { ReportsTo : 2 } }"); - results.Should().HaveCount(1); - } + var results = queryable.ToList(); + results.Should().HaveCount(1); + results[0].Id.Should().Be(1); + } - public class Employee - { - [BsonId] - public int EmployeeID { get; set; } - public int? ReportsTo { get; set; } + [Fact] + public void Equals_with_int32_and_nullable_int32_should_translate() + { + var collection = Fixture.Collection; + int intPrm = 2; + + var queryable = collection.AsQueryable() + .Where(e => e.ReportsTo.Equals(intPrm)); + + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match : { ReportsTo : 2 } }"); + + var results = queryable.ToList(); + results.Should().HaveCount(1); + results[0].Id.Should().Be(1); + } + + [Fact] + public void Equals_with_null_and_nullable_int32_should_translate() + { + var collection = Fixture.Collection; + + var queryable = collection.AsQueryable() + .Where(e => e.ReportsTo.Equals(null)); + + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match : { ReportsTo : null } }"); + + var results = queryable.ToList(); + results.Should().HaveCount(1); + results[0].Id.Should().Be(3); + } + + [Fact] + public void Equals_with_no_match_should_return_empty() + { + var collection = Fixture.Collection; + ulong longPrm = 999; + + var queryable = collection.AsQueryable() + .Where(e => e.ReportsTo.Equals(longPrm)); + + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match : { ReportsTo : 999 } }"); + + var results = queryable.ToList(); + results.Should().BeEmpty(); + } + + public class C + { + public int Id { get; set; } + public int? ReportsTo { get; set; } + } + + public sealed class ClassFixture : MongoCollectionFixture + { + protected override IEnumerable InitialData => + [ + new C { Id = 1, ReportsTo = 2 }, + new C { Id = 2, ReportsTo = 5 }, + new C { Id = 3, ReportsTo = null } + ]; + } } } From 0dcb81dba594f6c866cc9542a4b3499efe0ead6c Mon Sep 17 00:00:00 2001 From: Kyra Ramesh Krishna Date: Fri, 1 May 2026 17:00:04 -0400 Subject: [PATCH 4/5] add tests/change names --- .../EqualsMethodToFilterTranslatorTests.cs | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs index bcb8dd50f60..4fca41ae0f3 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs @@ -13,6 +13,7 @@ * limitations under the License. */ +using System; using System.Collections.Generic; using System.Linq; using FluentAssertions; @@ -31,10 +32,10 @@ public EqualsMethodToFilterTranslatorTests(ClassFixture fixture) : base(fixture) public void Equals_with_uint64_and_nullable_int32_should_translate() { var collection = Fixture.Collection; - ulong longPrm = 2; + ulong value = 2; var queryable = collection.AsQueryable() - .Where(e => e.ReportsTo.Equals(longPrm)); + .Where(e => e.ReportsTo.Equals(value)); var stages = Translate(collection, queryable); AssertStages(stages, "{ $match : { ReportsTo : 2 } }"); @@ -48,10 +49,10 @@ public void Equals_with_uint64_and_nullable_int32_should_translate() public void Equals_with_int32_and_nullable_int32_should_translate() { var collection = Fixture.Collection; - int intPrm = 2; + int value = 2; var queryable = collection.AsQueryable() - .Where(e => e.ReportsTo.Equals(intPrm)); + .Where(e => e.ReportsTo.Equals(value)); var stages = Translate(collection, queryable); AssertStages(stages, "{ $match : { ReportsTo : 2 } }"); @@ -77,14 +78,31 @@ public void Equals_with_null_and_nullable_int32_should_translate() results[0].Id.Should().Be(3); } + [Fact] + public void Equals_with_string_and_nullable_int32_should_throw() + { + var collection = Fixture.Collection; + var value = "2"; + + var queryable = collection.AsQueryable() + .Where(e => e.ReportsTo.Equals(value)); + + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match : { ReportsTo : 2 } }"); + + var results = queryable.ToList(); + results.Should().HaveCount(1); + results[0].Id.Should().Be(1); + } + [Fact] public void Equals_with_no_match_should_return_empty() { var collection = Fixture.Collection; - ulong longPrm = 999; + ulong value = 999; var queryable = collection.AsQueryable() - .Where(e => e.ReportsTo.Equals(longPrm)); + .Where(e => e.ReportsTo.Equals(value)); var stages = Translate(collection, queryable); AssertStages(stages, "{ $match : { ReportsTo : 999 } }"); @@ -93,6 +111,19 @@ public void Equals_with_no_match_should_return_empty() results.Should().BeEmpty(); } + [Fact] + public void Equals_with_overflowing_uint64_and_nullable_int32_should_throw() + { + var collection = Fixture.Collection; + ulong value = (ulong)int.MaxValue + 1; + + var queryable = collection.AsQueryable() + .Where(e => e.ReportsTo.Equals(value)); + + var exception = Record.Exception(() => Translate(collection, queryable)); + exception.Should().BeOfType(); + } + public class C { public int Id { get; set; } From 265d281bd011dbda7c0d7da350ca89d95aa296ba Mon Sep 17 00:00:00 2001 From: Ferdinando Papale <4850119+papafe@users.noreply.github.com> Date: Fri, 8 May 2026 15:30:11 +0200 Subject: [PATCH 5/5] Small fixes --- .../EqualsMethodToFilterTranslator.cs | 17 ++- .../EqualsMethodToFilterTranslatorTests.cs | 106 +++++------------- 2 files changed, 45 insertions(+), 78 deletions(-) diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslator.cs index 4629fff0608..44f623b00df 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslator.cs @@ -78,11 +78,26 @@ private static AstFilter Translate(TranslationContext context, Expression expres if (value != null && !serializerValueType.IsInstanceOfType(value)) { var targetType = Nullable.GetUnderlyingType(serializerValueType) ?? serializerValueType; - value = Convert.ChangeType(value, targetType); + var valueType = value.GetType(); + if (IsNumericType(valueType) && IsNumericType(targetType)) + { + value = Convert.ChangeType(value, targetType); + } } var serializedValue = SerializationHelper.SerializeValue(fieldTranslation.Serializer, value); return AstFilter.Eq(fieldTranslation.Ast, serializedValue); } + + private static bool IsNumericType(Type type) => + Type.GetTypeCode(type) switch + { + TypeCode.Byte or TypeCode.SByte or + TypeCode.Int16 or TypeCode.UInt16 or + TypeCode.Int32 or TypeCode.UInt32 or + TypeCode.Int64 or TypeCode.UInt64 or + TypeCode.Single or TypeCode.Double or TypeCode.Decimal => true, + _ => false + }; } } diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs index 4fca41ae0f3..9b0236a13f3 100644 --- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/EqualsMethodToFilterTranslatorTests.cs @@ -14,130 +14,82 @@ */ using System; -using System.Collections.Generic; -using System.Linq; using FluentAssertions; -using MongoDB.Driver.TestHelpers; +using MongoDB.Bson.Serialization; using Xunit; namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.MethodTranslators { - public class EqualsMethodToFilterTranslatorTests : LinqIntegrationTest + public class EqualsMethodToFilterTranslatorTests { - public EqualsMethodToFilterTranslatorTests(ClassFixture fixture) : base(fixture) - { - } + private static readonly RenderArgs __args = new( + BsonSerializer.SerializerRegistry.GetSerializer(), + BsonSerializer.SerializerRegistry); [Fact] - public void Equals_with_uint64_and_nullable_int32_should_translate() + public void Equals_with_uint64_and_nullable_int_should_translate() { - var collection = Fixture.Collection; ulong value = 2; - var queryable = collection.AsQueryable() - .Where(e => e.ReportsTo.Equals(value)); + var filter = Builders.Filter.Where(e => e.NullableIntegerProperty.Equals(value)); - var stages = Translate(collection, queryable); - AssertStages(stages, "{ $match : { ReportsTo : 2 } }"); - - var results = queryable.ToList(); - results.Should().HaveCount(1); - results[0].Id.Should().Be(1); + filter.Render(__args).Should().Be("{ NullableIntegerProperty : 2 }"); } [Fact] - public void Equals_with_int32_and_nullable_int32_should_translate() + public void Equals_with_int_and_nullable_int_should_translate() { - var collection = Fixture.Collection; int value = 2; - var queryable = collection.AsQueryable() - .Where(e => e.ReportsTo.Equals(value)); - - var stages = Translate(collection, queryable); - AssertStages(stages, "{ $match : { ReportsTo : 2 } }"); + var filter = Builders.Filter.Where(e => e.NullableIntegerProperty.Equals(value)); - var results = queryable.ToList(); - results.Should().HaveCount(1); - results[0].Id.Should().Be(1); + filter.Render(__args).Should().Be("{ NullableIntegerProperty : 2 }"); } [Fact] - public void Equals_with_null_and_nullable_int32_should_translate() + public void Equals_with_null_should_translate() { - var collection = Fixture.Collection; + var filter = Builders.Filter.Where(e => e.NullableIntegerProperty.Equals(null)); - var queryable = collection.AsQueryable() - .Where(e => e.ReportsTo.Equals(null)); - - var stages = Translate(collection, queryable); - AssertStages(stages, "{ $match : { ReportsTo : null } }"); - - var results = queryable.ToList(); - results.Should().HaveCount(1); - results[0].Id.Should().Be(3); + filter.Render(__args).Should().Be("{ NullableIntegerProperty : null }"); } [Fact] - public void Equals_with_string_and_nullable_int32_should_throw() + public void Equals_with_uint64_and_int_should_translate() { - var collection = Fixture.Collection; - var value = "2"; + ulong value = 1; - var queryable = collection.AsQueryable() - .Where(e => e.ReportsTo.Equals(value)); + var filter = Builders.Filter.Where(e => e.IntegerProperty.Equals(value)); - var stages = Translate(collection, queryable); - AssertStages(stages, "{ $match : { ReportsTo : 2 } }"); - - var results = queryable.ToList(); - results.Should().HaveCount(1); - results[0].Id.Should().Be(1); + filter.Render(__args).Should().Be("{ IntegerProperty : 1 }"); } [Fact] - public void Equals_with_no_match_should_return_empty() + public void Equals_with_string_and_nullable_int_should_throw() { - var collection = Fixture.Collection; - ulong value = 999; - - var queryable = collection.AsQueryable() - .Where(e => e.ReportsTo.Equals(value)); + var value = "2"; - var stages = Translate(collection, queryable); - AssertStages(stages, "{ $match : { ReportsTo : 999 } }"); + var filter = Builders.Filter.Where(e => e.NullableIntegerProperty.Equals(value)); - var results = queryable.ToList(); - results.Should().BeEmpty(); + var exception = Record.Exception(() => filter.Render(__args)); + exception.Should().NotBeNull(); } [Fact] - public void Equals_with_overflowing_uint64_and_nullable_int32_should_throw() + public void Equals_with_overflowing_uint64_and_nullable_int_should_throw() { - var collection = Fixture.Collection; ulong value = (ulong)int.MaxValue + 1; - var queryable = collection.AsQueryable() - .Where(e => e.ReportsTo.Equals(value)); + var filter = Builders.Filter.Where(e => e.NullableIntegerProperty.Equals(value)); - var exception = Record.Exception(() => Translate(collection, queryable)); + var exception = Record.Exception(() => filter.Render(__args)); exception.Should().BeOfType(); } - public class C - { - public int Id { get; set; } - public int? ReportsTo { get; set; } - } - - public sealed class ClassFixture : MongoCollectionFixture + public class TestClass { - protected override IEnumerable InitialData => - [ - new C { Id = 1, ReportsTo = 2 }, - new C { Id = 2, ReportsTo = 5 }, - new C { Id = 3, ReportsTo = null } - ]; + public int IntegerProperty { get; set; } + public int? NullableIntegerProperty { get; set; } } } }