diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs index cd5a08b37..75a160954 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs @@ -865,7 +865,14 @@ static Type FindTypeToInstantiate(Type collectionType, Type elementType) EnumDefinition? enumDefinition; if (storeType is null) { - enumDefinition = _enumDefinitions.SingleOrDefault(m => m.ClrType == clrType); + var matches = _enumDefinitions.Where(m => m.ClrType == clrType).ToList(); + if (matches.Count > 1) + { + throw new InvalidOperationException( + $"Multiple enum definitions registered for CLR type '{clrType?.FullName}'."); + } + + enumDefinition = matches.FirstOrDefault(); if (enumDefinition is null) { @@ -883,9 +890,16 @@ static Type FindTypeToInstantiate(Type collectionType, Type elementType) // names here, by parsing the name and stripping the quotes. ParseStoreTypeName(storeType, out name, out schema, out var size, out var precision, out var scale); - enumDefinition = schema is null - ? _enumDefinitions.SingleOrDefault(m => m.StoreTypeName == name) - : _enumDefinitions.SingleOrDefault(m => m.StoreTypeName == name && m.StoreTypeSchema == schema); + var matches = _enumDefinitions + .Where(m => m.StoreTypeName == name && (schema is null || m.StoreTypeSchema == schema)) + .ToList(); + if (matches.Count > 1) + { + throw new InvalidOperationException( + $"Multiple enum definitions registered for store type '{storeType}'."); + } + + enumDefinition = matches.FirstOrDefault(); if (enumDefinition is null) { diff --git a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs index 118a9bb58..2a8e7a7ea 100644 --- a/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs +++ b/test/EFCore.PG.Tests/Storage/NpgsqlTypeMappingSourceTest.cs @@ -313,6 +313,89 @@ private void Array_over_type_mapping_with_value_converter(CoreTypeMapping mappin #endregion Array + #region Enum + + [Fact] + public void Enum_mapping() + { + var source = CreateTypeMappingSource(configure: b => b.MapEnum("dummy_enum")); + + var mapping = source.FindMapping(typeof(DummyEnum)); + + Assert.IsType(mapping); + Assert.Equal("dummy_enum", mapping.StoreType); + Assert.Same(typeof(DummyEnum), mapping.ClrType); + Assert.Same(typeof(DummyEnum), source.FindMapping("dummy_enum").ClrType); + Assert.Same(typeof(DummyEnum), source.FindMapping(typeof(DummyEnum), "dummy_enum").ClrType); + Assert.Null(source.FindMapping("unknown_enum")); + Assert.Equal("integer", CreateTypeMappingSource().FindMapping(typeof(DummyEnum)).StoreType); + } + + [Theory] + [InlineData("dummy_enum", null)] + [InlineData("other_enum", null)] + [InlineData("dummy_enum", "other")] + public void Enum_with_multiple_definitions_for_clr_type(string secondName, string secondSchema) + { + var source = CreateTypeMappingSource( + configure: b => b.MapEnum("dummy_enum").MapEnum(secondName, secondSchema)); + + Assert.Equal( + $"Multiple enum definitions registered for CLR type '{typeof(DummyEnum).FullName}'.", + Assert.Throws(() => source.FindMapping(typeof(DummyEnum))).Message); + } + + [Theory] + [InlineData(null, null, "dummy_enum", typeof(DummyEnum))] + [InlineData(null, null, "dummy_enum", typeof(OtherDummyEnum))] + [InlineData("test", "test", "test.dummy_enum", typeof(DummyEnum))] + [InlineData("test", "test", "\"test\".\"dummy_enum\"", typeof(OtherDummyEnum))] + [InlineData("one", "two", "dummy_enum", typeof(OtherDummyEnum))] + public void Enum_with_multiple_definitions_for_store_type( + string firstSchema, string secondSchema, string storeType, Type secondClrType) + { + var source = CreateTypeMappingSource( + configure: b => b.MapEnum("dummy_enum", firstSchema) + .MapEnum(secondClrType, "dummy_enum", secondSchema)); + var expectedMessage = $"Multiple enum definitions registered for store type '{storeType}'."; + + Assert.Equal( + expectedMessage, + Assert.Throws(() => source.FindMapping(storeType)).Message); + Assert.Equal( + expectedMessage, + Assert.Throws(() => source.FindMapping(typeof(DummyEnum), storeType)).Message); + } + + [Theory] + [InlineData("one.dummy_enum", typeof(DummyEnum))] + [InlineData("two.dummy_enum", typeof(OtherDummyEnum))] + [InlineData("\"one\".\"dummy_enum\"", typeof(DummyEnum))] + public void Enum_mapping_disambiguated_by_schema(string storeType, Type clrType) + { + var source = CreateTypeMappingSource( + configure: b => b.MapEnum("dummy_enum", "one").MapEnum("dummy_enum", "two")); + + Assert.Same(clrType, source.FindMapping(storeType).ClrType); + Assert.Same(clrType, source.FindMapping(clrType, storeType).ClrType); + } + + [Theory] + [InlineData("first_enum")] + [InlineData("second_enum")] + public void Enum_mapping_disambiguated_by_store_type(string storeType) + { + var source = CreateTypeMappingSource( + configure: b => b.MapEnum("first_enum").MapEnum("second_enum")); + + var mapping = source.FindMapping(typeof(DummyEnum), storeType); + + Assert.Same(typeof(DummyEnum), mapping.ClrType); + Assert.Equal(storeType, mapping.StoreType); + } + + #endregion Enum + #region JSON [Fact] @@ -453,7 +536,8 @@ public void ParseStoreType(string storeTypeName, string expectedName, string? ex #region Support - private NpgsqlTypeMappingSource CreateTypeMappingSource(Version postgresVersion = null) + private NpgsqlTypeMappingSource CreateTypeMappingSource( + Version postgresVersion = null, Action configure = null) { var builder = new DbContextOptionsBuilder(); var npgsqlBuilder = new NpgsqlDbContextOptionsBuilder(builder); @@ -461,6 +545,7 @@ private NpgsqlTypeMappingSource CreateTypeMappingSource(Version postgresVersion npgsqlBuilder.MapRange("floatrange"); npgsqlBuilder.MapRange("dummyrange", subtypeName: "dummy"); npgsqlBuilder.SetPostgresVersion(postgresVersion); + configure?.Invoke(npgsqlBuilder); var options = new NpgsqlSingletonOptions(); options.Initialize(builder.Options); @@ -517,6 +602,16 @@ private class DummyType; private class UnknownType; + private enum DummyEnum + { + Value + } + + private enum OtherDummyEnum + { + Value + } + private class ByteArrayEntity { public int Id { get; set; }