How To: Example application with data access in F#

Friday, May 20, 2016

One of my biggest hurdles when starting with F# was how I should structure an actual application. There was documentation for individual libraries and scripts but not so much for app structure this post by Scott Wlaschin is fantastic, but is at a different level than I fully understood at that point in my F# journey.

The goal of this post is to provide a bridge to help developers new to F#.

Getting Started

The first thing to do will be to create a new F# application.

For this tutorial I'll use the Fsharp.Data.SqlClient type provider. You can install it through NuGet just as you would in a C# project.

PM> Install-Package FSharp.Data.SqlClient

Add a new file called DataAccess.fs and make sure it sits above Program.fs in your Solution Explorer. This is important because in an F# program the order of your files mimics the dependency order. This seemed silly to me at first but turns out to be pretty cool; it's always obvious where the low/high level stuff is in a project. Low level up top, high level on bottom.

Within DataAccess.fs we'll place our data access code. We'll need to add a reference to our Type Provider with open FSharp.Data and then we'll create a new type from that framework.

module DataAccess 
open FSharp.Data

type Create = 
    SqlCommandProvider<
        "INSERT INTO Locations(City, State)
        values(@city, @state)", 
        "name=local">

The second parameter to the SqlCommandProvider can be a connection string, but by using name= syntax the type provider will use the associated connectionString from your app/web.config file.

The sql defined in the Create type will be validated against the database with the named connection string at compile time

Next step is to provide a function that will use that type. Just below the type add

let createLocation city state =
    use command = new Create()
    command.Execute(city, state)

We now have a function that when given 2 string arguments will insert a row into our database whose connection string name is local.

Adding a business layer

As it's typical to have some amount of business layer in an app we'll add that next.

Create another file called Business.fs and move it between DataAccess.fs and Program.fs

Within this file we'll add the business logic that we want around inserting a location.

let insertLocation city state =
    if state |> String.length >= 2 
    then DataAccess.createLocation city state
    else raise (System.ArgumentException("Not a valid State"))

Easy enough. As you progress you'll probably want to write this code without raising an exception because that's not a very functional way of handling invalid arguments, but that's for a slightly more advanced post.

Calling the business layer

Now we're ready to call our business layer. We'll make our Program.fs file look like this

module Main 
    
[<EntryPoint>]
let main argv = 
    Business.insertLocation "Knoxville" "TN"

Putting it all together

//DataAccess.fs
module DataAccess =
    open FSharp.Data

    type Create = 
        SqlCommandProvider<
            "INSERT INTO Locations(City, State)
            values(@city, @state)", 
            "name=local">

    let createLocation city state =
        use command = new Create()
        command.Execute(city, state)

//Business.fs
module Business =
    let insertLocation city state =
        if state |> String.length >= 2 
        then DataAccess.createLocation city state
        else raise (System.ArgumentException("Not a valid State"))

//Program.fs
module Main =

    [<EntryPoint>]
    let main argv = 
        Business.insertLocation "Knoxville" "TN"