Skip to content

Instantly share code, notes, and snippets.

@mwyrebski
Created February 11, 2021 08:30
Show Gist options
  • Select an option

  • Save mwyrebski/d8da0a3954123a7781643738981bc217 to your computer and use it in GitHub Desktop.

Select an option

Save mwyrebski/d8da0a3954123a7781643738981bc217 to your computer and use it in GitHub Desktop.
Results multimapping a.k.a master-detail in F#

Results multimapping a.k.a master-detail in F#

Let's consider how we can query for the relational data given by these sample types:

Company
- Id
- Name
- Country

Employee
- CompanyId
- Firstname
- Lastname

They are in relation where employees belong to a certain company identified by the CompanyId.

How can we map the complete set of data retrieved in a single query?

We can introduce monoidical types for Company and Employee and use grouping.

Let's consider results retrieved with a query like this:

select Id, Name, Country, Firstname, Lastname from Company inner join Employee on Id = CompanyId;

And let's consider we have them as a list of DTO values:

type CompanyReportView =
    { Id: int
      Name: string
      Country: string
      Firstname: string
      Lastname: string }

As a result, we want to provide us with a report:

type Report = { AllCompanies: Company list }

and Company =
    { Name: string
      Country: string
      Employees: Employee list }

and Employee = { Firstname: string; Lastname: string }

Given sample data:

let list: CompanyReportView list =
    [ { Id = 123
        Name = "Berechtigtes Interesse"
        Country = "DE"
        Firstname = "Alex"
        Lastname = "Lange " }
      { Id = 123
        Name = "Berechtigtes Interesse"
        Country = "DE"
        Firstname = "Oscar"
        Lastname = "Vogel" }
      { Id = 123
        Name = "Berechtigtes Interesse"
        Country = "DE"
        Firstname = "Max"
        Lastname = "Werner" } ]

We can prepare a report:

module Report =

    let private toCompanyDto (item: CompanyReportView) =
        { Name = item.Name
          Country = item.Country
          Employees = [] }

    let private toEmployeeDto (item: CompanyReportView) =
        { Firstname = item.Firstname
          Lastname = item.Lastname }

    let prepare (list: CompanyReportView list): Report =
        let pairsByCompany =
            list
            |> List.map (fun item -> toCompanyDto item, toEmployeeDto item)
            |> List.groupBy fst

        let companies =
            pairsByCompany
            |> List.map
                (fun (company, pairsByCompany) ->
                    let employees = pairsByCompany |> List.map snd
                    { company with Employees = employees })

        { AllCompanies = companies }

Finally, let report = Report.prepare list will print:

val report : Report =
  { AllCompanies =
                  [{ Name = "Berechtigtes Interesse"
                     Country = "DE"
                     Employees =
                                [{ Firstname = "Alex"
                                   Lastname = "Lange " };
                                 { Firstname = "Oscar"
                                   Lastname = "Vogel" };
                                 { Firstname = "Max"
                                   Lastname = "Werner" }] }] }
type CompanyReportView =
{ Id: int
Name: string
Country: string
Firstname: string
Lastname: string }
type Report = { AllCompanies: Company list }
and Company =
{ Name: string
Country: string
Employees: Employee list }
and Employee = { Firstname: string; Lastname: string }
let list: CompanyReportView list =
[ { Id = 1
Name = "Berechtigtes Interesse"
Country = "DE"
Firstname = "Alex"
Lastname = "Lange " }
{ Id = 1
Name = "Berechtigtes Interesse"
Country = "DE"
Firstname = "Oscar"
Lastname = "Vogel" }
{ Id = 1
Name = "Berechtigtes Interesse"
Country = "DE"
Firstname = "Max"
Lastname = "Werner" } ]
module Report =
let private toCompanyDto (item: CompanyReportView) =
{ Name = item.Name
Country = item.Country
Employees = [] }
let private toEmployeeDto (item: CompanyReportView) =
{ Firstname = item.Firstname
Lastname = item.Lastname }
let prepare (list: CompanyReportView list): Report =
let pairsByCompany =
list
|> List.map (fun item -> toCompanyDto item, toEmployeeDto item)
|> List.groupBy fst
let companies =
pairsByCompany
|> List.map
(fun (company, pairsByCompany) ->
let employees = pairsByCompany |> List.map snd
{ company with Employees = employees })
{ AllCompanies = companies }
let report = Report.prepare list
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment