告别JSON字段映射烦恼:Dapper中JsonTypeHandler的优雅实现

告别JSON字段映射烦恼:Dapper中JsonTypeHandler的优雅实现

【免费下载链接】Dapper 【免费下载链接】Dapper 项目地址: https://gitcode.com/gh_mirrors/dappe/dapper-dot-net

你是否还在为数据库JSON字段与C#对象的繁琐转换而头疼?当ORM框架无法自动处理复杂JSON结构时,手动序列化/反序列化不仅代码冗余,还容易引发性能问题。本文将带你用100行代码实现Dapper的JSON类型处理器(JsonTypeHandler),彻底解决JSON字段映射难题。读完你将掌握:

  • 自定义类型处理器(TypeHandler)的核心原理
  • 通用JSON处理器的实现模板
  • 性能优化与缓存策略
  • 完整的单元测试方案

Dapper类型处理机制解析

Dapper作为轻量级ORM(对象关系映射)工具,通过类型处理器(TypeHandler)实现数据库类型与C#类型的双向转换。其核心接口定义在Dapper/SqlMapper.TypeHandler.cs中,包含两个关键方法:

public abstract class TypeHandler<T> : ITypeHandler
{
    public abstract void SetValue(IDbDataParameter parameter, T value);
    public abstract T Parse(object value);
}
  • SetValue:将C#对象序列化为数据库字段值(如JSON字符串)
  • Parse:将数据库字段值(如JSON字符串)反序列化为C#对象

Dapper已内置多种类型处理器,如Dapper.EntityFramework/DbGeographyHandler.cs实现了地理空间类型的处理。我们将基于此机制构建JSON专用处理器。

通用JsonTypeHandler实现

基础版实现

使用System.Text.Json或Newtonsoft.Json实现JSON序列化,以下是基于System.Text.Json的通用处理器:

using System.Data;
using System.Text.Json;
using Dapper;

public class JsonTypeHandler<T> : SqlMapper.TypeHandler<T>
{
    private readonly JsonSerializerOptions _options;

    public JsonTypeHandler(JsonSerializerOptions options = null)
    {
        _options = options ?? new JsonSerializerOptions 
        { 
            PropertyNameCaseInsensitive = true 
        };
    }

    public override void SetValue(IDbDataParameter parameter, T value)
    {
        parameter.DbType = DbType.String;
        parameter.Value = value == null 
            ? DBNull.Value 
            : JsonSerializer.Serialize(value, _options);
    }

    public override T Parse(object value)
    {
        if (value == null || value is DBNull)
            return default;
            
        return JsonSerializer.Deserialize<T>((string)value, _options);
    }
}

注册与使用

在应用启动时注册处理器:

// 全局注册
SqlMapper.AddTypeHandler(new JsonTypeHandler<ProductMetadata>());

// 或使用特定配置
var options = new JsonSerializerOptions { WriteIndented = true };
SqlMapper.AddTypeHandler(new JsonTypeHandler<OrderDetails>(options));

实体类中直接使用复杂类型:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ProductMetadata Metadata { get; set; } // JSON字段
}

public class ProductMetadata
{
    public Dictionary<string, string> Features { get; set; }
    public decimal[] PriceHistory { get; set; }
}

数据库操作示例:

// 插入带JSON字段的记录
connection.Execute(@"
    INSERT INTO Products (Name, Metadata) 
    VALUES (@Name, @Metadata)",
    new Product 
    { 
        Name = "Smartphone",
        Metadata = new ProductMetadata 
        {
            Features = new Dictionary<string, string>
            {
                { "Storage", "128GB" },
                { "Camera", "48MP" }
            },
            PriceHistory = new[] { 599.99m, 549.99m }
        }
    });

// 查询并自动反序列化
var product = connection.QueryFirst<Product>(
    "SELECT * FROM Products WHERE Id = @Id", 
    new { Id = 1 });

高级特性与性能优化

缓存策略

对于高频访问的JSON结构,可添加缓存层减少序列化开销:

public class CachedJsonTypeHandler<T> : JsonTypeHandler<T>
{
    private readonly ConcurrentDictionary<string, T> _cache = new();

    public override T Parse(object value)
    {
        var json = (string)value;
        if (_cache.TryGetValue(json, out var cached))
            return cached;
            
        var result = base.Parse(value);
        _cache.TryAdd(json, result);
        return result;
    }
}

支持不可变类型

通过构造函数参数支持不可变对象:

public class ImmutableProductMetadata
{
    public ImmutableProductMetadata(
        Dictionary<string, string> features, 
        decimal[] priceHistory)
    {
        Features = features;
        PriceHistory = priceHistory;
    }

    public Dictionary<string, string> Features { get; }
    public decimal[] PriceHistory { get; }
}

// 使用时指定构造函数
var options = new JsonSerializerOptions
{
    Converters = { new JsonStringEnumConverter() },
    IncludeFields = true
};

单元测试实现

参考tests/Dapper.Tests/TypeHandlerTests.cs中的测试模式,为JsonTypeHandler编写测试:

public class JsonTypeHandlerTests
{
    [Fact]
    public void Serialize_ComplexObject_ShouldStoreAsJsonString()
    {
        // Arrange
        var handler = new JsonTypeHandler<TestObject>();
        var testObj = new TestObject { Id = 1, Name = "Test" };
        var parameter = new Mock<IDbDataParameter>();

        // Act
        handler.SetValue(parameter.Object, testObj);

        // Assert
        parameter.VerifySet(p => p.DbType = DbType.String);
        parameter.VerifySet(p => p.Value = JsonSerializer.Serialize(testObj));
    }

    [Fact]
    public void Deserialize_JsonString_ShouldReturnObject()
    {
        // Arrange
        var handler = new JsonTypeHandler<TestObject>();
        var json = JsonSerializer.Serialize(new TestObject { Id = 1, Name = "Test" });

        // Act
        var result = handler.Parse(json);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(1, result.Id);
        Assert.Equal("Test", result.Name);
    }
}

public class TestObject
{
    public int Id { get; set; }
    public string Name { get; set; }
}

常见问题解决方案

日期时间处理

解决JSON日期格式问题:

var options = new JsonSerializerOptions
{
    Converters = { new DateTimeConverterUsingDateTimeParse() }
};

public class DateTimeConverterUsingDateTimeParse : JsonConverter<DateTime>
{
    public override DateTime Read(
        ref Utf8JsonReader reader, 
        Type typeToConvert, 
        JsonSerializerOptions options)
    {
        return DateTime.Parse(reader.GetString());
    }

    public override void Write(
        Utf8JsonWriter writer, 
        DateTime value, 
        JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
    }
}

枚举处理

使用JsonStringEnumConverter实现枚举与字符串的转换:

var options = new JsonSerializerOptions
{
    Converters = { new JsonStringEnumConverter() }
};

public enum Status { Active, Inactive }

public class Entity 
{
    public Status CurrentStatus { get; set; }
}

总结与最佳实践

  1. 性能优化

    • 对高频访问类型使用缓存处理器
    • 共享JsonSerializerOptions实例减少分配
  2. 错误处理

    • 添加异常处理和日志记录
    • 实现自定义异常类型如JsonTypeHandlerException
  3. 扩展性

    • 通过泛型约束支持特定接口
    • 实现IDisposable处理非托管资源
  4. 兼容性

    • 对旧系统使用Newtonsoft.Json实现
    • 对.NET Core 3.0+优先使用System.Text.Json

通过JsonTypeHandler,Dapper能够优雅处理JSON字段,既保留了SQL的灵活性,又获得了ORM的便利性。完整实现可参考项目测试用例tests/Dapper.Tests/TypeHandlerTests.cs中的StringListTypeHandler等示例。

项目官方文档:docs/index.md 类型处理器源码:Dapper/SqlMapper.TypeHandler.cs

【免费下载链接】Dapper 【免费下载链接】Dapper 项目地址: https://gitcode.com/gh_mirrors/dappe/dapper-dot-net

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值