Skip to content

类型映射

这一页关注业务类型和数据库存储类型之间的转换,例如值对象、JSON 字段、自定义枚举封装。

适用场景

  • 实体字段不是数据库原生标量类型
  • 需要把对象序列化后存到字符串或 JSON 列
  • 想让查询、参数绑定、更新都共用同一套转换规则

核心问题

SqlSharp 通过 TypeMapperValueConverter 管理自定义类型映射。

核心思想是:

  • 业务代码使用领域类型
  • 持久化时转换成数据库能存的类型
  • 查询回来时再转换回业务类型

快速开始

cangjie
import sqlsharp.converters.*

@Serialization
public class Address {
    private var _city: String = ""
    private var _street: String = ""
}

public class AddressJsonConverter <: ValueConverter<Address, String> {
    public override func convertToProvider(value: Address): String {
        JsonSerializer.serializeObject(value)
    }

    public override func convertFromProvider(value: String): Address {
        JsonSerializer.deserializeObject<Address>(value)
    }
}

DbContext 里注册:

cangjie
public override func onConfiguring(b: DbContextOptionsBuilder): Unit {
    let mapper = TypeMapper()
    mapper.register<Address, String>(AddressJsonConverter())
    b.useTypeMapper(mapper)
}

核心概念

注册后,这个转换会同时参与:

  • 实体查询
  • DTO 查询
  • SQL 参数绑定
  • 更改跟踪
  • executeScalar<T>()

这意味着类型映射一旦定义好,就不需要在每个查询里重复手工转换。

常见用法

JSON 字段映射

最常见的场景是把对象序列化存到 JSON 列:

cangjie
// 实体定义
@Entity
@Serialization
public class User {
    private var _id: Int64 = 0
    private var _address: Address = Address()
}

// 注册转换器
public override func onConfiguring(b: DbContextOptionsBuilder): Unit {
    let mapper = TypeMapper()
    mapper.register<Address, String>(AddressJsonConverter())
    b.useTypeMapper(mapper)
}

// 查询时自动反序列化
let user = ctx.users.find([Int64(1)]).getOrThrow()
let city = user.address.city  // 直接访问领域对象

数组字段映射

cangjie
public class TagsConverter <: ValueConverter<ArrayList<String>, String> {
    public override func convertToProvider(value: ArrayList<String>): String {
        value.join(",")
    }

    public override func convertFromProvider(value: String): String {
        value.split(",")
    }
}

mapper.register<ArrayList<String>, String>(TagsConverter())

与其他模块组合

  • 映射注册入口在 DbContext 配置
  • 手写 SQL 和 Queryable 都会复用这套转换规则
  • 如果类型还要参与序列化映射,本身仍应具备相应的序列化能力

生产建议

  • 先明确数据库实际存什么,再设计转换器
  • 转换器逻辑尽量保持纯粹,不要混入业务判断
  • JSON 存储方便,但也要评估查询能力和索引能力

常见问题

转换器能同时用于读写吗?

能。ValueConverter<TModel, TProvider> 同时定义了 convertToProvider(写)和 convertFromProvider(读),注册一次同时覆盖查询和写入。

JSON 字段能参与 WHERE 查询吗?

能,但取决于数据库的 JSON 函数支持。例如 MySQL 的 JSON_EXTRACT、MariaDB 的 JSON_VALUE 可以在手写 SQL 中使用。Queryable 的 whereIf 无法直接操作 JSON 内部字段,这种情况切到 手写 SQL