TP

Dynamic in F#: Reading data from SQL database

When reading data from a SQL database in F#, you have a couple of options. You can use F# implementation of LINQ, which allows you to write queries directly in the F# language or you can use standard ADO.NET classes such as SqlCommand. The second option is often good enough - when you just need to call a simple query, you probably don't want to setup the whole LINQ infrastructure (generate classes, reference F# PowerPack, etc.) Unfortunately, ADO.NET classes are a bit ugly to use. When calling an SQL stored procedure, you need to specify the name of the procedure as a string, you need to add parameters one-by-one by creating instances of SqlParameter, you need to dynamically cast results from the obj type and so on...

In this article, we'll look how to use the dynamic operator to make the experience of using ADO.NET from F# dramatically better. Dynamic operator (there are actually two of them) are a simple way of supporting dynamic invoke in F#. We can use it to write code that looks almost like an ordinary method call or property access, but is resolved dynamically at runtime (using the name of the method or property). The following example shows what we'll be able to write at the end of this article:

// Call 'GetProducts' procedure with 'CategoryID' set to 1
use conn = new DynamicSqlConnection(connectionString)
use cmd = conn?GetProducts
cmd?CategoryID <- 1
conn.Open()

// Read all products and print their names
use reader = cmd.ExecuteReader()
while reader.Read() do
  printfn "Product: %s" reader?ProductName

If you ever tried to call a SQL stored procedure directly using the SqlCommand, then you can surely appreciate the elegance of this code snippet. Let's now take a look at a larger example and some of the neat tricks that make this possible...

Calling stored procedure: The usual way

We'll start the article by looking at a single, more complete, example of calling an SQL stored procedure. I wrote this while porting the Personal Web Site Starter Kit to F# (which I'll describe in the next blog post). The project has a simple photo gallery that contains albums and each album contains some photos. When reading photos from the database, we can nicely store them in the following F# record type:

// Record type that represents photo with caption
type Photo = 
  { PhotoID:int 
    PhotoAlbumID:int
    PhotoCaption:string }

Now that we have a type to represent photos, we can look at a function that reads photos from the database. Our database has an SQL stored procedure called GetPhotos that takes two parameters. The parameter AlbumID specifies the ID of the album and the parameter IsPublic specifies whether we want to get only publicly accessible photos in the gallery. The following function calls the stored procedure and reads the returned photos. The whole function body is wrapped in a sequence expression, so the return type will be seq<Photo>:

// Function that obtains photos in a specified album from database
let GetPhotos (albumID:int) = seq { 
  // Create a command to call SQL stored procedure
  use conn = new SqlConnection(connectionString)
  use cmd = new SqlCommand("GetPhotos", conn)
  cmd.CommandType <- CommandType.StoredProcedure

  // Add parameters to the stored procedure call
  cmd.Parameters.Add(SqlParameter("@@AlbumID", albumID)) |> ignore
  cmd.Parameters.Add(SqlParameter("@@IsPublic", true)) |> ignore
  
  // Run the command and read results into an F# record
  conn.Open()
  use reader = cmd.ExecuteReader()
  while reader.Read() do
    yield { PhotoID = unbox (reader.["PhotoID"])
            PhotoAlbumID = unbox (reader.["AlbumID"])
            PhotoCaption = unbox (reader.["Caption"])} } 

As you can see, we first create an instance of the SqlConnection type, then create a new command and set its type to StoredProcedure. Then we need to specify parameters of the procedure, which is done by calling cmd.Parameters.Add, which takes SqlParameter as an argument. To read the result, we get SqlDataReader and then repeatedly call Read. When processing a single result row, we need to use the indexer to access individual columns of the result by name. The indexer returns the data as value of type obj, so we use unbox to cast the result to the type expected by the record.

There are no particularly tricky places in the code, but if you look at it, there is quite a lot of noise that doesn't express anything important. For example, we need to create SqlCommand explicitly (giving it the name of the procedure as a string). When adding a parameter, we need to create an object (giving it the name of the parameter as a string), call a method and ignore the result. And finally, when reading the results, we need to insert explicit conversion.

You could try writing a function for creating the SQL command to make the code simple. However, there are still some problems. For example, if you decided to pass parameters of the procedure as a list of name-value pairs, the value would have to be of type obj and you would need to add explicit boxing. Luckily, we can throw all this noise away using the dynamic invoke operator...

Calling stored procedure: Using dynamic operator

Let's look how to write the same function using the dynamic operator. In order to make this work, you need to get an implementation of the operator that is available at the end of the article. The file with the implementation also defines several wrapper types that add the dynamic functionality to standard ADO.NET types.

Instead of creating standard SqlConnection, we need to create an instance of the DynamicSqlConnection type (we'll look at why this is needed and how does it work soon). Once we do this change, we can use ? to create commands representing SQL procedure calls, to specify parameters of the procedure and also to access columns when reading the result:

// Function that obtains photos in a specified album from database
let GetPhotos (albumID:int) = seq { 
  use conn = new DynamicSqlConnection(connectionString)
  use cmd = conn?GetPhotos
  cmd?AlbumID <- albumID
  cmd?IsPublic <- true

  // Run the command and read results into an F# record
  conn.Open()
  use reader = cmd.ExecuteReader()
  while (reader.Read()) do
    yield { PhotoID = reader?PhotoID 
            PhotoAlbumID = reader?AlbumID
            PhotoCaption = reader?Caption } } 

Let's now look at the most interesting places in the code where we use the dynamic invoke operator:

Looking under the cover

We're using the ? operator in two ways. It can be called on an object representing SQL connection (e.g. conn?GetPhotos) in which case it returns a command for calling a stored procedure. It can be also called on an object representing SQL data reader (e.g. reader?PhotoID) in which case it accesses column from the result set. As a result, we cannot write a single global declaration of the operator (using the let keyword), but we need to associate the implementation with some type. However, we cannot add operators to existing .NET types, so we create simple wrappers for the well-known ADO.NET types.

Let's start by looking at the DynamicSqlDataReader type, which wraps a SqlDataReader. We didn't need to use this type directly in the previous examples, because the wrapping is done automatically - when we invoke ExecuteReader on a wrapped command object, it automatically wraps the returned data reader. The implementation is slightly simplified (you can get a full version at the end of the article), but it shows the two most important things. Firstly, we expose the method Read for iterating through the result set and secondly, we implement the ? operator for accessing results. (Note that the operator must be static, so we need to expose the wrapped reader using a private property):

// SqlDataReader wrapper that provides access to columns 
// of the result-set using dynamic access operator
type DynamicSqlDataReader(reader:SqlDataReader) =
  member private x.Reader = reader
  member x.Read() = reader.Read
  // Read the specified column and casts it to the specified type
  static member (?) (dr:DynamicSqlDataReader, name:string) : 'R = 
    unbox (dr.Reader.[name])

The implementation of the dynamic operator is quite simple. The only interesting aspect is the fact that the operator is generic (in the return type) and that it uses unbox to convert the obtained value from obj to the type (which will be deduced by F# type inference based on how we use the result). The next wrapper wraps the SqlCommand type and adds a support for adding parameters using the ?<- operator:

// SqlCommand wrapper that allows setting properties of a
// stored procedure using dynamic setter operator
type DynamicSqlCommand(cmd:SqlCommand) = 
  member private x.Command = cmd
  // Adds parameter with the specified name and value
  static member (?<-) (cmd:DynamicSqlCommand, name:string, value) = 
    let p = SqlParameter("@@" + name, box value)
    cmd.Command.Parameters.Add(p) |> ignore
  // Execute command and wrap returned SqlDataReader
  member x.ExecuteReader() = 
    new DynamicSqlDataReader(cmd.ExecuteReader())

The implementation is again pretty straightforward. Note that we need to provide an operator named ?<-, because F# uses different dynamic access operator when reading properties and when setting value of a property. The operator takes three parameters - the target command (which needs to expose the underlying command), a name of the property and a value to be used. We also expose the ExecuteReader method, which automatically wraps the returned reader (so that we can dynamically access columns).

The last wrapper is the only one that needs to be created explicitly. It represents connection to the database and provides an implementation of the ? operator that can be used for creating commands that call SQL stored procedure (the returned command is automatically wrapped using the previous type):

// SqlConnection wrapper that allows creating stored 
// procedure calls using the dynamic access operator
type DynamicSqlConnection(connStr:string) =
  let conn = new SqlConnection(connStr)
  member private x.Connection = conn
  member x.Open() = conn.Open()
  // Creates command that calls the specified stored procedure
  static member (?) (conn:DynamicSqlConnection, name) = 
    let command = new SqlCommand(name, conn.Connection)
    command.CommandType <- CommandType.StoredProcedure
    new DynamicSqlCommand(command)

The type exposes the Open method of the underlying connection and implements the dynamic access operator ?. The implementation of the operator constructs a new command, specifies its type and wraps it using the DynamicSqlCommand type (so that we can set parameters of the stored procedure call).

Summary & downloads

In this article, we've seen how to make accessing data using ADO.NET from F# nicer using dynamic operators. This allows us to reduce the number of noise that has to be written in tasks related to data-access such as creating SQL commands, specifying parameters of a stored procedure and reading data from the returned result-set.

We've also seen one useful technique of implementing dynamic access in general. Because we needed to provide different implementation of the operator for different types, we couldn't use a global implementation (written using the let keyword). Instead, we implemented the operator in several wrappers that wrap standard ADO.NET classes and augment them with the dynamic features. I believe that this technique can be quite useful in other related scenarios.

Below you can download the implementation of the wrappers that were used and presented in this article. The download doesn't contain any examples (you can use the example from the article as the starting point). I'll post an ASP.NET project that uses the wrappers in another blog post soon...

Published: Friday, 9 July 2010, 2:03 AM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: functional, web, f#