From a08c652986497fd3b41141894e7223f76ef13e53 Mon Sep 17 00:00:00 2001 From: Max Gruebner Date: Wed, 10 Jun 2026 15:38:17 +1200 Subject: [PATCH] fix issue with .Contains not mapping types correctly, fix type mappings for collated expressions --- .../Internal/MySQLSqlExpressionFactory.cs | 54 ++++++++++++++++++- .../BasicGuidTests.cs | 54 ++++++++++++------- .../MySql.EFCore.Basic.Tests/EFCoreTests.cs | 12 +++++ 3 files changed, 100 insertions(+), 20 deletions(-) diff --git a/EFCore/src/Query/Internal/MySQLSqlExpressionFactory.cs b/EFCore/src/Query/Internal/MySQLSqlExpressionFactory.cs index 2e22aac0c..e4d0eded7 100644 --- a/EFCore/src/Query/Internal/MySQLSqlExpressionFactory.cs +++ b/EFCore/src/Query/Internal/MySQLSqlExpressionFactory.cs @@ -236,6 +236,51 @@ private SqlExpression ApplyNewTypeMapping(SqlExpression sqlExpression, Relationa _ => base.ApplyTypeMapping(sqlExpression, typeMapping) }; + +#if NET10_0_OR_GREATER + /// + /// Overrides the base In(item, valuesParameter) so that the resulting 's + /// always carries a non-null type mapping. + /// + /// + /// + /// The base implementation looks up a collection type mapping via + /// TypeMappingSource.FindMapping(valuesParameter.Type, model, elementMapping). + /// This can leave the values parameter unmapped if the parameter is an IEnumerable + /// + /// + public override SqlExpression In(SqlExpression item, SqlParameterExpression valuesParameter) + { + var inExpression = (InExpression)base.In(item, valuesParameter); + + if (inExpression.ValuesParameter is not { TypeMapping: null } unmappedValuesParameter) + { + return inExpression; + } + + var model = Dependencies.Model; + var elementMapping = inExpression.Item.TypeMapping + ?? _typeMappingSource.FindMapping(inExpression.Item.Type, model); + if (elementMapping is null) + { + return inExpression; + } + + // Back-apply the element mapping to the item if it was missing one. + var mappedItem = inExpression.Item.TypeMapping is null + ? ApplyTypeMapping(inExpression.Item, elementMapping) + : inExpression.Item; + + var collectionMapping = _typeMappingSource.FindMapping(unmappedValuesParameter.Type, model, elementMapping); + if (collectionMapping is null) + { + collectionMapping = (RelationalTypeMapping)elementMapping.Clone(elementMapping: elementMapping); + } + + return inExpression.Update(mappedItem, unmappedValuesParameter.ApplyTypeMapping(collectionMapping)); + } +#endif + private SqlBinaryExpression ApplyTypeMappingOnSqlBinary(SqlBinaryExpression sqlBinaryExpression, RelationalTypeMapping typeMapping) { // The default SqlExpressionFactory behavior is to assume that the two operands have the same type, and so to infer one side's @@ -292,8 +337,13 @@ private MySQLComplexFunctionArgumentExpression ApplyTypeMappingOnComplexFunction private MySQLCollateExpression ApplyTypeMappingOnCollate(MySQLCollateExpression collateExpression) { -#if NET9_0_OR_GREATER - return new MySQLCollateExpression(collateExpression.Operand, collateExpression.Charset, collateExpression.Collation); +#if NET9_0_OR_GREATER + var inferredTypeMapping = ExpressionExtensions.InferTypeMapping(collateExpression.Operand) + ?? _typeMappingSource.FindMapping(collateExpression.Operand.Type); + return new MySQLCollateExpression( + ApplyTypeMapping(collateExpression.Operand, inferredTypeMapping), + collateExpression.Charset, + collateExpression.Collation); #else var inferredTypeMapping = ExpressionExtensions.InferTypeMapping(collateExpression.ValueExpression) ?? _typeMappingSource.FindMapping(collateExpression.ValueExpression.Type); diff --git a/EFCore/tests/MySql.EFCore.Basic.Tests/BasicGuidTests.cs b/EFCore/tests/MySql.EFCore.Basic.Tests/BasicGuidTests.cs index d95335ac3..3ec0f0e0b 100644 --- a/EFCore/tests/MySql.EFCore.Basic.Tests/BasicGuidTests.cs +++ b/EFCore/tests/MySql.EFCore.Basic.Tests/BasicGuidTests.cs @@ -53,24 +53,42 @@ public void OneTimeTearDown() public void TestEmptyGUID() { MySQLTestStore.Execute("drop database if exists DbContextGuid; Create database DbContextGuid; "); - using (var context = new ContextGUID()) - { - context.Database.EnsureDeleted(); - context.Database.EnsureCreated(); - var filter = new[] { Guid.Empty }; - var resultFilter = context.Guidtable.Where(t => filter.Contains(t.Uuid)).ToArray(); - Assert.That(resultFilter, Is.Not.Null); - Random rnd = new Random(); - var guid = Guid.NewGuid(); - var record = new GuidTable { Id = rnd.Next(100), Uuid = guid }; - context.Guidtable.Add(record); - context.SaveChanges(); - var rows = context.Guidtable.Count(); - Assert.That(rows, Is.EqualTo(1)); - filter[0] = guid; - var resultFilter2 = context.Guidtable.Where(t => filter.Contains(t.Uuid)).ToArray(); - Assert.That(resultFilter2.Count(), Is.EqualTo(1)); - } + using var context = new ContextGUID(); + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + var filter = new[] { Guid.Empty }; + var resultFilter = context.Guidtable.Where(t => filter.Contains(t.Uuid)).ToArray(); + Assert.That(resultFilter, Is.Not.Null); + Random rnd = new Random(); + var guid = Guid.NewGuid(); + var record = new GuidTable { Id = rnd.Next(100), Uuid = guid }; + context.Guidtable.Add(record); + context.SaveChanges(); + var rows = context.Guidtable.Count(); + Assert.That(rows, Is.EqualTo(1)); + filter[0] = guid; + var resultFilter2 = context.Guidtable.Where(t => filter.Contains(t.Uuid)).ToArray(); + Assert.That(resultFilter2.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestGuidContains() + { + MySQLTestStore.Execute("drop database if exists DbContextGuid; Create database DbContextGuid; "); + using var context = new ContextGUID(); + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + var guid1 = Guid.NewGuid(); + var guid2 = Guid.NewGuid(); + var guidTable1 = new GuidTable() { Id = 1, Uuid = guid1 }; + var guidTable2 = new GuidTable() { Id = 2, Uuid = guid2 }; + context.Guidtable.Add(guidTable1); + context.Guidtable.Add(guidTable2); + context.SaveChanges(); + var filter = new[] { guid1 }; + var resultFilter = context.Guidtable.Where(t => filter.Contains(t.Uuid)).ToArray(); + Assert.That(resultFilter.Count(), Is.EqualTo(1)); + Assert.That(resultFilter.First(), Is.EqualTo(guidTable1)); } public class GuidTable diff --git a/EFCore/tests/MySql.EFCore.Basic.Tests/EFCoreTests.cs b/EFCore/tests/MySql.EFCore.Basic.Tests/EFCoreTests.cs index dfabdaef4..7911a5dd9 100644 --- a/EFCore/tests/MySql.EFCore.Basic.Tests/EFCoreTests.cs +++ b/EFCore/tests/MySql.EFCore.Basic.Tests/EFCoreTests.cs @@ -292,5 +292,17 @@ public void MultipleSchemaContext() } } } + + [Test] + public void StringComparisonCheck() + { + using (SakilaLiteContext context = new SakilaLiteContext()) + { + context.InitContext(); + + var test = context.Customer.Where(x => x.LastName!.StartsWith("SMIT", StringComparison.OrdinalIgnoreCase)).ToList(); + Assert.That(test.Count, Is.GreaterThan(0)); + } + } } }