Question

I'm new to F# and trying to dive in first and do a more formal introduction later. I have the following code:

type Person = 
    {
        Id: int
        Name: string
    }

let GetPeople() =
    //seq {

    use conn = new SQLiteConnection(connectionString)
    use cmd = new SQLiteCommand(sql, conn)

    cmd.CommandType <- CommandType.Text

    conn.Open()
    use reader = cmd.ExecuteReader()
    let mutable x = {Id = 1; Name = "Mary"; }

    while reader.Read() do
        let y = 0
        // breakpoint here
        x <- {
        Id = unbox<int>(reader.["id"])
        Name = unbox<string>(reader.["name"])
        }
    x

    //}

let y = GetPeople()

I plan to replace the loop body with a yield statement and clean up the code. But right now I'm just trying to make sure the data access works by debugging the code and looking at the datareader. Currently I'm getting a System.InvalidCastException. When I put a breakpoint at the point indicated by the commented line above, and then type in the immediate windows reader["name"] I get a valid value from the database so I know it's connecting to the db ok. However if I try to put reader["name"] (as opposed to reader.["name"]) in the source file I get "This value is not a function and cannot be applied" message.

Why can I use reader["name"] in the immediate window but not in my fsharp code? How can I use string indexing with the reader?

Update

Following Jack P.'s advice I split out the code into separate lines and now I see where the error occurs:

    let id = reader.["id"]
    let id_unboxed = unbox id // <--- error on this line

id has the type object {long} according to the debugger.

Was it helpful?

Solution 2

You can use reader["name"] in the immediate window because the immediate window uses C# syntax, not F# syntax.

One thing to note: since F# is much more concise than C#, there can be a lot going on within a single line. In other words, setting a breakpoint on the line may not help you narrow down the problem. In those cases, I normally "expand" the expression into multiple let-bindings on multiple lines; doing this makes it easier to step through the expression and find the cause of the problem (at which point, you can just make the change to your original one-liner).

What happens if you pull the item accesses and unbox calls out into their own let-bindings? For example:

while reader.Read() do
    let y = 0
    // breakpoint here
    let id = reader.["id"]
    let id_unboxed : int = unbox id
    let name = reader.["name"]
    let name_unboxed : string = unbox name
    x <- { Id = id_unboxed; Name = name_unboxed; }
x

OTHER TIPS

Jack already answered the question regarding different syntax for indexing in F# and in the immediate window or watches, so I'll skip that.

In my experience, the most common reason for getting System.InvalidCastException when reading data from a database is that the value returned by reader.["xyz"] is actually DbNull.Value instead of an actual string or integer. Casting DbNull.Value to integer or string will fail (because it is a special value), so if you're working with nullable columns, you need to check this explicitly:

let name = reader.["name"]
let name_unboxed : string = 
  if name = DbNull.Value then null else unbox name

You can make the code nicer by defining the ? operator which allows you to write reader?name to perform the lookup. If you're dealing with nulls you can also use reader?name defaultValue with the following definition:

let (?) (reader:IDataReader) (name:string) (def:'R) : 'R =
  let v = reader.[name]
  if Object.Equals(v, DBNull.Value) then def
  else unbox v

The code then becomes:

let name = reader?name null
let id = reader?id -1

This should also simplify debugging as you can step into the implementation of ? and see what is going on.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top