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

使用Dapper映射DBNull时如何抛出异常并实现字段非空校验?

问题描述

假设我有一个要求字段值非默认值的实体类:

public class MyModel
{
    public int PropertyOne { get; set; }
    public int? PropertyTwo { get; set; }
    public string PropertyThree { get; set; }
}

当执行以下查询语句查询MyModel数据时:

SELECT
   NULL as PropertyOne,
   NULL as PropertyTwo,
   NULL as PropertyThree

映射后的结果为PropertyOne=0,PropertyTwo=null,PropertyThree=null。原因是Dapper默认遇到DBNull时不会调用属性的setter,因此值类型int的PropertyOne会被设为默认值0。

我的需求如下:

  1. 确保PropertyOne始终从查询中获取非空值,当读取到DBNull时抛出异常。
  2. 不希望PropertyThree为null,能否在Dapper映射时实现该校验?能否通过给实体类添加自定义[NotDBNull]特性实现?
    修改后的实体类如下:
public class MyModel
{
    public int PropertyOne { get; set; }
    public int? PropertyTwo { get; set; }
    [NotDBNull]
    public string PropertyThree { get; set; }
}

理想的校验逻辑:

  • 场景1:PropertyOne读取到DBNull且该字段非可空 → 抛出异常
  • 场景2:PropertyTwo读取到DBNull且该字段可空 → 正常处理
  • 场景3:PropertyThree读取到DBNull且标记了[NotDBNull]特性 → 抛出异常

请问能否不借助其他校验方式,仅通过Dapper实现上述需求?


解决方案

可以仅通过Dapper的扩展机制实现你的需求,核心是利用自定义类型处理器和自定义映射逻辑覆盖默认行为:

1. 实现自定义[NotDBNull]特性

先定义需要的校验特性:

[AttributeUsage(AttributeTargets.Property)]
public class NotDBNullAttribute : Attribute
{
}

2. 自定义ITypeMap实现全局校验逻辑

通过实现ITypeMap拦截属性赋值过程,嵌入空值校验逻辑,这是最灵活的方式:

public class StrictTypeMap : DefaultTypeMap
{
    public StrictTypeMap(Type type) : base(type)
    {
    }

    public override PropertyMap GetProperty(string columnName)
    {
        var propertyMap = base.GetProperty(columnName);
        if (propertyMap == null) return null;

        // 包装原Setter,添加校验逻辑
        var originalSetter = propertyMap.Setter;
        propertyMap.Setter = (target, value) =>
        {
            var propertyInfo = propertyMap.PropertyInfo;
            
            // 场景1:非可空值类型遇到DBNull
            if (!IsNullableType(propertyInfo.PropertyType) && value == DBNull.Value)
            {
                throw new InvalidOperationException($"字段 {columnName} 读取到DBNull,但属性 {propertyInfo.Name} 为非可空类型,无法赋值。");
            }
            
            // 场景3:标记[NotDBNull]的属性遇到DBNull
            if (propertyInfo.GetCustomAttribute<NotDBNullAttribute>() != null && value == DBNull.Value)
            {
                throw new InvalidOperationException($"字段 {columnName} 读取到DBNull,但属性 {propertyInfo.Name} 标记了[NotDBNull]特性,无法赋值。");
            }
            
            // 场景2:可空类型处理DBNull为null
            originalSetter(target, value == DBNull.Value ? null : value);
        };

        return propertyMap;
    }

    // 辅助方法:判断类型是否为可空类型
    private static bool IsNullableType(Type type)
    {
        return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
    }
}

3. 注册自定义类型映射

在应用启动时,将自定义映射注册到Dapper,针对MyModel生效:

SqlMapper.SetTypeMap(typeof(MyModel), new StrictTypeMap(typeof(MyModel)));

备选方案:针对特定类型的自定义处理器

如果只需要单独处理非可空值类型(比如int),可以实现SqlMapper.TypeHandler<T>:

public class StrictIntHandler : SqlMapper.TypeHandler<int>
{
    public override void SetValue(IDbDataParameter parameter, int value)
    {
        parameter.Value = value;
    }

    public override int Parse(object value)
    {
        if (value == DBNull.Value)
        {
            throw new InvalidOperationException("读取到DBNull,无法转换为非可空int类型。");
        }
        return Convert.ToInt32(value);
    }
}

注册处理器:

SqlMapper.AddTypeHandler(new StrictIntHandler());

这种方式仅能处理指定值类型,无法覆盖[NotDBNull]标记的引用类型,因此更推荐第一种ITypeMap方案。

验证效果

执行原查询时:

  • PropertyOne读取到DBNull,因是非可空int会抛出异常;
  • PropertyTwo读取到DBNull,因是可空int?正常赋值为null;
  • PropertyThree读取到DBNull,因标记[NotDBNull]会抛出异常;
    完全符合需求。

内容的提问来源于stack exchange,提问作者Anton Zhernosek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 02:13:13