Reputation: 5384
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
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