Как использовать индексацию строк с IDataReader в F#?

Я новичок в F# и пытаюсь сначала погрузиться, а потом сделать более формальное введение. У меня есть следующий код:

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()

Я планирую заменить тело цикла оператором yield и очистить код. Но сейчас я просто пытаюсь убедиться, что доступ к данным работает, отлаживая код и просматривая устройство чтения данных. В настоящее время я получаю System.InvalidCastException. Когда я ставлю точку останова в точку, указанную в строке с комментариями выше, а затем ввожу непосредственные окна reader["name"], я получаю действительное значение из базы данных, поэтому я знаю, что он подключается к базе данных в порядке. Однако, если я пытаюсь поместить reader["name"] (в отличие от reader.["name"]) в исходный файл, я получаю сообщение «Это значение не является функцией и не может быть применено».

Почему я могу использовать reader["name"] в непосредственном окне, но не в моем коде fsharp? Как я могу использовать индексацию строк с читатель?

Обновить

Следуя совету Джека П., я разбил код на отдельные строки и теперь вижу, где возникает ошибка:

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

id имеет тип object {long} согласно отладчику.


person User    schedule 06.03.2013    source источник


Ответы (2)


Вы можете использовать reader["name"] в непосредственном окне, поскольку непосредственное окно использует синтаксис C#, а не синтаксис F#.

Следует отметить одну вещь: поскольку F# гораздо более лаконичен, чем C#, в одной строке может быть много всего. Другими словами, установка точки останова на строке может не помочь вам сузить круг проблемы. В таких случаях я обычно «расширяю» выражение на несколько привязок let в нескольких строках; это упрощает пошаговое выполнение выражения и поиск причины проблемы (в этот момент вы можете просто внести изменения в исходный однострочный текст).

Что произойдет, если вы поместите доступ к элементу и вызовы unbox в свои собственные let-привязки? Например:

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
person Jack P.    schedule 06.03.2013
comment
Это было полезно. В строке let id_unboxed = unbox id ошибка. Глядя на тип id в отладчике, он показывает object {long}. В ближайшем окне id is long возвращает true, а id is int возвращает false. Так что я предполагаю, что это как-то связано с int и длинным литьем. Буду экспериментировать дальше. - person User; 07.03.2013
comment
@User Если это long, вам нужно распаковать его как int64 (синтаксис F# для long). Я думаю, вам нужно будет распаковать его в правильный тип (int64) затем преобразовать его в int, если это то, что вам нужно — вы не можете просто распаковать его напрямую (это вызывает ошибку, которую вы сейчас наблюдаемся). - person Jack P.; 07.03.2013
comment
Если я изменю свой тип Person, чтобы использовать int64 вместо int, это сработает. Не уверен, почему он тянет идентификатор так долго из БД. - person User; 07.03.2013
comment
Только что узнал, что SQLite использует первичные ключи целых чисел int64, что объясняет это: sqlite.org/version3.html - person User; 07.03.2013

Джек уже ответил на вопрос о различном синтаксисе индексации в F# и непосредственном окне или часах, поэтому я пропущу это.

По моему опыту, наиболее распространенная причина получения System.InvalidCastException при чтении данных из базы данных заключается в том, что значение, возвращаемое reader.["xyz"], на самом деле является DbNull.Value, а не фактической строкой или целым числом. Приведение DbNull.Value к целому числу или строке завершится ошибкой (поскольку это специальное значение), поэтому, если вы работаете со столбцами, допускающими значение NULL, вам нужно проверить это явно:

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

Вы можете сделать код лучше, определив оператор ?, который позволяет вам написать reader?name для выполнения поиска. Если вы имеете дело с нулями, вы также можете использовать reader?name defaultValue со следующим определением:

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

Затем код становится:

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

Это также должно упростить отладку, поскольку вы можете перейти к реализации ? и посмотреть, что происходит.

person Tomas Petricek    schedule 06.03.2013