Please provide non-generic methods for creating and mapping enums. · Issue #3383 · npgsql/npgsql · GitHub
Skip to content

Please provide non-generic methods for creating and mapping enums. #3383

Description

@andrewslavin

Currently suggested ways to create and map enums are:

ModelBuilder.HasPostgresEnum<TEnum>() //to create enum from DbContext.OnModelCreating()
NpgsqlConnection.GlobalTypeMapper.MapEnum<TEnum>() //to map enum

Can you please provide non-generic versions:

ModelBuilder.HasPostgresEnum(Type enumType)
NpgsqlConnection.GlobalTypeMapper.MapEnum(Type enumType)

This would allow to loop through properties of CLR types that represent DB entities and create+map all enums automatically, like so:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    foreach (Type enumType in
        modelBuilder.Model.GetEntityTypes()
            .SelectMany(et => et.ClrType.GetProperties().Select(pi => pi.PropertyType).Where(t => t.IsEnum)))
    {
        //create db type from enum type
        modelBuilder.HasPostgresEnum(enumType);

        //map enum type to db type
        NpgsqlConnection.GlobalTypeMapper.MapEnum(enumType);
    }
}

I have achieved the above using reflection, which looks pretty ugly:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    MethodInfo hasPostgresEnumMethodInfo = typeof(NpgsqlModelBuilderExtensions)
        .GetMethods()
        .Single(mi => mi.Name == nameof(NpgsqlModelBuilderExtensions.HasPostgresEnum) && mi.IsGenericMethod);

    INpgsqlTypeMapper typeMapper = NpgsqlConnection.GlobalTypeMapper;
    MethodInfo mapEnumMethodInfo = typeMapper.GetType().GetMethod(nameof(typeMapper.MapEnum));

    foreach (Type enumType in
        modelBuilder.Model.GetEntityTypes()
            .SelectMany(et => et.ClrType.GetProperties().Select(pi => pi.PropertyType).Where(t => t.IsEnum)))
    {
        //create db type from enum type
        hasPostgresEnumMethodInfo.MakeGenericMethod(enumType).Invoke(null, new object[] {modelBuilder, null, null, null});

        //map enum type to db type
        mapEnumMethodInfo.MakeGenericMethod(enumType).Invoke(typeMapper, new object[] {null, null});
    }
}

If there's a better way, please let me know.

Activity

  1. roji commented on Nov 29, 2020

    @roji
    Member

    Makes sense - we have non-generic mapping methods for composite, but not for enum. It should be pretty easy to do if you want to submit a PR.

  2. transferred this issue fromnpgsql/efcore.pgon Nov 29, 2020
  3. added this to the vNext milestone on Nov 29, 2020
  4. andrewslavin commented on Nov 29, 2020

    @andrewslavin
    Author

    @roji Thank you for prompt response.

    It should be pretty easy to do if you want to submit a PR.

    I actually started doing that. Looked at this method:


    and got stuck on the line that creates new EnumTypeHandlerFactory<TEnum>, because could not quickly find a way to create
    non-generic TypeHandlerFactory.

    On a different note, as part of this change it would be really handy to also add an extension method .AddEnumsFromClrTypes to the ModelBuilder. Implementation is just the loop in the original question.

  5. roji commented on Nov 29, 2020

    @roji
    Member

    and got stuck on the line that creates new EnumTypeHandlerFactory

    Ah yes, you're right - the type handler system is (at least currently) generic. It should be OK to use reflection to make a generic type for EnumTypeHandlerFactory, much as you're doing outside of Npgsql. At some point in the future we may change the type handler system to not require this any more.

    On a different note, as part of this change it would be really handy to also add an extension method .AddEnumsFromClrTypes to the ModelBuilder. Implementation is just the loop in the original question.

    Would that do anything more than loop and call MapEnum? If so, then it doesn't seem like something that should be inside the provider.

  6. andrewslavin commented on Nov 29, 2020

    @andrewslavin
    Author

    Would that do anything more than loop and call MapEnum?

    That would call two things in the loop: modelBuilder.HasPostgresEnum(enumType) and NpgsqlConnection.GlobalTypeMapper.MapEnum(enumType).

    If so, then it doesn't seem like something that should be inside the provider.

    Yeah, not sure... Definitely not in this repo. Maybe could be a class ModelBuilderExtensions in https://github.com/npgsql/efcore.pg. Ability to do the below in your derived DbContext is too tempting :) :

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ConfigureEnumsFromEntities(); //creates and maps all enums found in entity CLR types
    
        ...the rest of OnModelCreating code
    }
    
  7. roji commented on Nov 29, 2020

    @roji
    Member

    OK. Let's start with non-generic enum mapping, I want to take another look at how we handle enum mappings in EFCore.PG in any case.

  8. modified the milestones: 6.0.0, 7.0.0 on Oct 5, 2021
  9. BogdanYarotsky commented on Sep 3, 2022

    @BogdanYarotsky
    Contributor

    Hi! @roji, is this issue still relevant and a "good first issue"? I'm looking for a ticket to make my first contribution to npgsql.

  10. roji commented on Sep 3, 2022

    @roji
    Member

    @BogdanYarotsky sure, I guess so - you should be able to do the same thing that the non-generic MapComposite does.

  11. BogdanYarotsky commented on Sep 3, 2022

    @BogdanYarotsky
    Contributor

    @roji Thanks for the quick reply! I will start tomorrow

  12. modified the milestones: 7.0.0, 8.0.0 on Nov 9, 2022
  13. added a commit that references this issue on Nov 7, 2023
    ab0b668
  14. added theissue type on Jun 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions