如何将数据库记录读入可区分的联合?

How to read a database record into a discriminated union?

假设我在 F# 中有以下内容:

type PersonName = {
    birthDate : DateTime
    firstName : string
    lastName : string
}

后跟的判别并集:

type Visit = 
| Scheduled of name: PersonName * appointmentTime: DateTime
| WalkIn of name: PersonName * appointmentTime: DateTime * timeOfService: DateTime
| KeptAppointment of name: PersonName * appointmentTime: DateTime * postingTime: DateTime * serviceTime: DateTime
| OpenSlot of appointmentTime: DateTime

with 
  member x.Name = 
      match x with
      | Scheduled(name, _)
      | WalkIn(name, _, _)
      | KeptAppointment(name, _, _, _) -> Some name
      | OpenSlot _ -> None

如果 Visit 被定义为一个简单的记录,例如:

type Visit = {
    lastName : string
    firstName : string
    birthDate : DateTime
    appointmentTime : Nullable<DateTime>
    tservice : Nullable<DateTime>
    postingTime : Nullable<DateTime>
    chartNumber : Nullable<int>
    }

然后从数据库服务器读取的以下内容正常工作:

/// Get the office schedule for the tableDate.
    let GetScheduleAsync (tableDate : DateTime) =
        async {
            let! data = context.GetOfficeScheduleAsync(tableDate) |> Async.AwaitTask
            return data |> Seq.map(fun q -> {
                Visit.lastName = q.lastname
                firstName = q.firstname
                birthDate = q.birthdate
                appointmentTime = q.appointment_time
                tservice = q.service_time
                postingTime = q.posting_time
                chartNumber = q.chart_number
                })                  
          }
          |> Async.StartAsTask

但是,如果使用 Visit 的第一个版本(可区分联合),“编译器”无法识别如何处理 PersonName 的出生日期、名字和姓氏。显然,我没有正确传达我的意图。

那么,我如何将数据库记录读入已区分的联合?

有许多不同的方法可以做到这一点;所有这些都与大多数 ORM 处理继承的方式非常相似 类。然而,考虑到目前数据库的状态,还有其他方法。

从根本上说,所有联合都是由给定“标签”分派的一组类型。对于每个 tag/union 案例,都有一组必须定义的字段(记录)。因此,您需要在 map lambda 中使用一种方法来检查某种条件并适当地创建正确的联合类型。

let GetScheduleAsync (tableDate : DateTime) =
        async {
            let! data = context.GetOfficeScheduleAsync(tableDate) |> Async.AwaitTask
            return data |> Seq.map(fun q -> 
                match q.Tag with
                | x when x = "Scheduled" -> Scheduled({ firstName = q.firstName; lastName = q.lastName; birthDate = q.BirthDate }, q.appointmentTime)
                | _ -> failwithf "Insert other cases here"
                )                  
          }
          |> Async.StartAsTask

如何确定“标签”实际上是数据库设计的问题,有许多不同的策略可以做到这一点(例如,单个 table 带有标签字段,Table 每个具体 class/tag).某些数据库还允许 JSON 序列化(例如 Postgres),如果您只想 serialise/deserialise 将联合直接放入字段中,这可能是一个选项。如何在数据库中表示数据以及如何将其读入联合构造函数完全取决于您。