From 532cb4fbb13dc68e932ce261a0df80f84dadc4f7 Mon Sep 17 00:00:00 2001 From: vadivelselva83-dotcom Date: Sat, 10 Oct 2026 17:23:12 -0400 Subject: [PATCH] Map the BOOL and BOOLEAN store type aliases without a size postfix HasColumnType("boolean") on a bool property generated `boolean(1)`, which MySQL rejects. The type mapping source had no entry for the aliases, so the lookup fell back to the default bool mapping (tinyint with size 1) and the size postfix was applied to the configured store type name. Register `bool` and `boolean` as bool mappings without a size. See PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#2045. Co-Authored-By: Claude Opus 5.5 --- .../Internal/MySqlTypeMappingSource.cs | 7 ++ .../MySqlBooleanStoreTypeAliasTest.cs | 108 ++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 test/EFCore.MySql.Tests/Migrations/MySqlBooleanStoreTypeAliasTest.cs diff --git a/src/EFCore.MySql/Storage/Internal/MySqlTypeMappingSource.cs b/src/EFCore.MySql/Storage/Internal/MySqlTypeMappingSource.cs index fbabe6b41..c6bb37973 100644 --- a/src/EFCore.MySql/Storage/Internal/MySqlTypeMappingSource.cs +++ b/src/EFCore.MySql/Storage/Internal/MySqlTypeMappingSource.cs @@ -19,6 +19,8 @@ public class MySqlTypeMappingSource : RelationalTypeMappingSource // boolean private readonly MySqlBoolTypeMapping _bit1 = new MySqlBoolTypeMapping("bit", size: 1); private readonly MySqlBoolTypeMapping _tinyint1 = MySqlBoolTypeMapping.Default; + private readonly MySqlBoolTypeMapping _bool = new MySqlBoolTypeMapping("bool"); + private readonly MySqlBoolTypeMapping _boolean = new MySqlBoolTypeMapping("boolean"); // bit private readonly MySqlULongTypeMapping _bit = new MySqlULongTypeMapping("bit"); @@ -265,6 +267,11 @@ private void Initialize() _storeTypeMappings[bit1AsBool ? "bit(1)" : "tinyint(1)"] = new RelationalTypeMapping[] { bit1AsBool ? _bit1 : _tinyint1 }; _clrTypeMappings[typeof(bool)] = bit1AsBool ? _bit1 : _tinyint1; + + // BOOL and BOOLEAN are synonyms for TINYINT(1), that do not accept a display width. Without their own mappings, they + // would fall back to the default CLR type mapping and inherit its size postfix (resulting in e.g. `boolean(1)`). + _storeTypeMappings["bool"] = new RelationalTypeMapping[] { _bool }; + _storeTypeMappings["boolean"] = new RelationalTypeMapping[] { _boolean }; } // Guid diff --git a/test/EFCore.MySql.Tests/Migrations/MySqlBooleanStoreTypeAliasTest.cs b/test/EFCore.MySql.Tests/Migrations/MySqlBooleanStoreTypeAliasTest.cs new file mode 100644 index 000000000..26d5af21e --- /dev/null +++ b/test/EFCore.MySql.Tests/Migrations/MySqlBooleanStoreTypeAliasTest.cs @@ -0,0 +1,108 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage; +using Xunit; + +namespace Microting.EntityFrameworkCore.MySql.Migrations +{ + public sealed class MySqlBooleanStoreTypeAliasTest + : TestWithFixture + { + public MySqlBooleanStoreTypeAliasTest(MySqlBooleanStoreTypeAliasFixture fixture) + : base(fixture) + { + } + + [ConditionalTheory] + [InlineData("boolean")] + [InlineData("BOOLEAN")] + [InlineData("bool")] + [InlineData("BOOL")] + public void Boolean_alias_store_type_is_not_suffixed_with_size(string storeType) + { + using var context = Fixture.CreateContext(); + + var mapping = context.GetService().FindMapping(typeof(bool), storeType); + + Assert.NotNull(mapping); + Assert.Equal(storeType, mapping.StoreType); + Assert.Equal(typeof(bool), mapping.ClrType); + } + + [ConditionalFact] + public void Default_and_explicit_tinyint_store_types_are_unchanged() + { + using var context = Fixture.CreateContext(); + var typeMappingSource = context.GetService(); + + Assert.Equal("tinyint(1)", typeMappingSource.FindMapping(typeof(bool)).StoreType); + Assert.Equal("tinyint(1)", typeMappingSource.FindMapping(typeof(bool), "tinyint(1)").StoreType); + } + + [ConditionalFact] + public void Columns_configured_with_boolean_aliases_keep_the_configured_store_type() + { + using var context = Fixture.CreateContext(); + + var columns = context.GetService().Model + .GetRelationalModel() + .Tables.Single() + .Columns.ToDictionary(c => c.Name, c => c.StoreType); + + Assert.Equal("boolean", columns[nameof(Model.Switch.Boolean)]); + Assert.Equal("bool", columns[nameof(Model.Switch.Bool)]); + Assert.Equal("tinyint(1)", columns[nameof(Model.Switch.Default)]); + } + + [ConditionalFact] + public void Table_with_boolean_alias_columns_can_be_created_and_round_trips_values() + { + using var context = Fixture.CreateContext(); + + // The fixture has already created the table. Before the aliases were mapped, that failed with a syntax error near "(1)". + var switches = context.Set().OrderBy(s => s.Id).ToList(); + + Assert.Equal(2, switches.Count); + Assert.True(switches[0].Boolean); + Assert.False(switches[0].Bool); + Assert.False(switches[1].Boolean); + Assert.True(switches[1].Bool); + + Assert.Equal(1, context.Set().Count(s => s.Boolean && !s.Bool)); + } + + public class MySqlBooleanStoreTypeAliasFixture + : MySqlTestFixtureBase + { + public class MySqlBooleanStoreTypeAliasContext : ContextBase + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity( + entity => + { + entity.Property(e => e.Boolean).HasColumnType("boolean"); + entity.Property(e => e.Bool).HasColumnType("bool"); + + entity.HasData( + new Model.Switch { Id = 1, Boolean = true, Bool = false, Default = true }, + new Model.Switch { Id = 2, Boolean = false, Bool = true, Default = false }); + }); + } + } + } + + private static class Model + { + public class Switch + { + public int Id { get; set; } + public bool Boolean { get; set; } + public bool Bool { get; set; } + public bool Default { get; set; } + } + } + } +}