首页 > 解决方案 > 如何将数据库记录读入有区别的联合?

问题描述

假设我在 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 的生日、名字和姓氏。显然,我没有正确传达我的意图。

那么,如何将数据库记录读入有区别的联合呢?

标签: f#discriminated-union

解决方案


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

从根本上说,所有联合都是由给定“标签”调度的一组类型。对于每个标签/联合案例,都有一组必须定义的字段(记录)。因此,您需要在 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

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


推荐阅读