TL;DR
内容
リレーショナルデータベースを扱う の一節である。選択型も RDB からの読み取り と同じ手順で読み取れるが、少し複雑になる。
例: 1 件の Contact を読み取る
ContactId を使って 1 件の Contact を取得する readOneContact 関数を実装する。型は 選択型をテーブルにマッピングする手法 のものを使い、DB ではすべてのケースを同じテーブルに格納している。
type Contact =
{ ContactId: ContactId
Info: ContactInfo }
and ContactInfo =
| Email of EmailAddress
| Phone of PhoneNumber
and EmailAddress = EmailAddress of string
and PhoneNumber = PhoneNumber of string
and ContactId = ContactId of int1. クエリの型を定義する
type ReadOneContact = SqlCommandProvider<"""
SELECT ContactId, IsEmail, IsPhone, EmailAddress, PhoneNumber
FROM ContactInfo
WHERE ContactId = @contactId
""", connectionString>2. toDomain を実装する
フラグを調べて ContactInfo のケースを決め、内側の result 式で各ケースのデータを組み立てる。
let toDomain (dbRecord: ReadOneContact.Record): Result<Contact,_> =
result {
let! contactId =
dbRecord.ContactId
|> ContactId.create
let! contactInfo =
if dbRecord.IsEmail then
result {
let! emailAddressString =
dbRecord.EmailAddress
|> Result.ofOption "Email expected to be non null"
let! emailAddress =
emailAddressString |> EmailAddress.create
return (Email emailAddress)
}
else
result {
let! phoneNumberString =
dbRecord.PhoneNumber
|> Result.ofOption "PhoneNumber expected to be non null"
let! phoneNumber =
phoneNumberString |> PhoneNumber.create
return (Phone phoneNumber)
}
let contact =
{ ContactId = contactId
Info = contactInfo }
return contact
}Result.ofOption は、Option を Result に変換するヘルパ関数である。
module Result =
let ofOption errorValue opt =
match opt with
| Some v -> Ok v
| None -> Error errorValue3. readOneContact を実装する
RDB からの読み取りで作った convertSingleDbRecord を使うと、次のように短く書ける。
let readOneContact (productionConnection: SqlConnection) (ContactId contactId) =
use cmd = new ReadOneContact(productionConnection)
let tableName = "ContactInfo"
let records = cmd.Execute(contactId = contactId) |> Seq.toList
convertSingleDbRecord tableName contactId records toDomainORM を使わない理由
toDomain が複雑なので、ORM を使いたくなる。しかし ORM では、メールアドレスや注文数の検証、ネストした選択型の処理ができない。ドメインの整合性を保てないので、使わないほうがよい。