You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Dapper和Npgsql实现PostgreSQL枚举与带自定义特性的.NET枚举自动映射问题

使用Dapper和Npgsql实现PostgreSQL枚举与带自定义特性的.NET枚举自动映射问题

看起来你遇到的核心问题是自定义的Dapper TypeHandler没有被触发,这其实和Npgsql本身对PostgreSQL枚举的处理逻辑有关——Npgsql会优先接管枚举类型的转换,导致Dapper的TypeHandler没机会介入。下面我来一步步帮你解决这个问题:

一、先搞清楚为什么TypeHandler没触发

当你使用NpgsqlConnection连接PostgreSQL时,Npgsql内置了对PostgreSQL枚举类型的处理逻辑:它会尝试直接将数据库中的枚举值映射到.NET枚举。默认情况下,它是按枚举成员名称的字符串匹配来映射的,但你的.NET枚举成员名(比如test1)和PostgreSQL里的枚举值(test 1)不匹配,所以默认映射会失败;同时,因为Npgsql已经在处理这个类型转换了,Dapper的自定义TypeHandler就不会被调用,这就是你断点没命中的原因。

二、解决方案:让Npgsql使用自定义特性来映射枚举

我们需要告诉Npgsql如何根据你的PgName(以及Description)特性来匹配枚举值,而不是依赖默认的名称匹配。具体步骤如下:

1. 实现枚举映射的扩展方法

首先写一个通用的扩展方法,用来注册.NET枚举和PostgreSQL枚举的自定义映射规则:

using System.ComponentModel;
using System.Reflection;
using Npgsql;
using NpgsqlTypes;

public static class NpgsqlEnumMappingExtensions
{
    public static void RegisterPostgresEnumWithCustomAttributes<TEnum>(this INpgsqlTypeMapper typeMapper, string postgresEnumFullName)
        where TEnum : struct, Enum
    {
        // 映射规则:优先取PgName特性值,其次取Description特性值,最后用枚举成员名
        typeMapper.MapEnum<TEnum>(postgresEnumFullName, enumMember =>
        {
            var fieldInfo = typeof(TEnum).GetField(enumMember.ToString());
            if (fieldInfo == null) return enumMember.ToString();

            // 先找PgName特性
            var pgNameAttr = fieldInfo.GetCustomAttribute<PgNameAttribute>();
            if (pgNameAttr != null) return pgNameAttr.Name;

            // 再找Description特性
            var descriptionAttr = fieldInfo.GetCustomAttribute<DescriptionAttribute>();
            if (descriptionAttr != null) return descriptionAttr.Description;

            // 都没有就用默认成员名
            return enumMember.ToString();
        });
    }
}

2. 在测试初始化时注册Npgsql枚举映射

修改你的SetUp方法,在打开连接之前先注册Npgsql的枚举映射(注意PostgreSQL枚举的全名要包含schema,比如integration_tests.test_enum):

[OneTimeSetUp]
protected virtual void SetUp()
{
    DefaultTypeMap.MatchNamesWithUnderscores = true;
    
    // 先注册Npgsql的枚举自定义映射
    NpgsqlConnection.GlobalTypeMapper.RegisterPostgresEnumWithCustomAttributes<TestEnum>("integration_tests.test_enum");
    
    // 如果你仍然需要Dapper处理参数写入,可以保留这行
    SqlMapper.AddTypeHandler(typeof(TestEnum), new EnumMapper<TestEnum>());

    LoadPostgresDatabaseFromAppsettings("AMDB", "DefaultConnection");
}

3. 调整Dapper TypeHandler(可选)

如果你的场景需要Dapper来处理枚举的写入(比如参数传递),可以保留你的EnumMapper<T>,但需要补充空值处理逻辑:

public override void SetValue(IDbDataParameter parameter, T? value)
{
    Console.WriteLine($"EnumMapper<T>.SetValue called for {typeof(T).Name}: {value}");
    if (value == null)
    {
        parameter.Value = DBNull.Value;
        return;
    }
    parameter.Value = value.GetPgName() ?? value.GetDescription() ?? value.ToString();
}

三、验证测试

现在重新运行你的测试,Npgsql会根据你定义的PgName特性来匹配PostgreSQL枚举值,同时如果需要Dapper处理参数,TypeHandler也会被正确触发。你的测试应该能成功将数据库中的testtt映射到TestEnum.test2。

额外注意点

  • 确保PostgreSQL枚举的全名(schema + 枚举名)完全正确,否则Npgsql找不到对应的枚举类型。
  • 如果是在生产环境中,建议在应用启动时就注册Npgsql的枚举映射,而不是测试初始化时。
  • 如果你不需要Dapper处理枚举的读写(Npgsql已经能完成映射),可以去掉Dapper的TypeHandler注册,减少冗余。

备注:内容来源于stack exchange,提问作者kevingoos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 11:49:31