Alan Wayne
Alan Wayne

Reputation: 5384

How to read a database record into a discriminated union?

Let's say I have the following in F#:

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

Followed by the discriminated union of:

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

If Visit is defined as a simple record, like:

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

Then the following read from the database server works correctly:

/// 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

However, if the first version of Visit (the discriminated union) is used, the "compiler" does not recognize what to do with the birthdate, firstname, and lastname of the PersonName. Clearly, I am not correctly communicating my intent.

So, how do I read a database record into the discriminated union?

Upvotes: 0

Views: 392

Answers (1)

akara
akara

Reputation: 406

There's a number of different methods to do this; all very similar to how most ORM's deal with inheritance classes. However given the state of databases these days there are other methods as well.

Fundamentally all a union is a set of types dispatched by a given "Tag". For each tag/union case there is set of fields (record) that has to be defined. Therefore you need a way in your map lambda to check some sort of condition and create the correct union type appropriately.

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

How you determine the "Tag" is really a question of database design and there is a number of different strategies to do that (e.g. Single table with tag field, Table per concrete class/tag). Some databases also allow JSON serialisation (e.g. Postgres) which can be an option if you just want to serialise/deserialise the union directly into a field. Its entirely up to you how you represent the data in the database and therefore how you read it into the union constructors.

Upvotes: 1

Related Questions