F # лучший способ настроить SQLCommand с параметрами

Моя программа F # должна общаться с SQL Server. В одной части у меня есть что-то вроде этого:

 let workFlowDetailRuncommand = new SqlCommand(query, econnection) 
    workFlowDetailRuncommand.CommandTimeout <- 100000
    workFlowDetailRuncommand.Parameters.Add("@1", SqlDbType.Int).Value <- 42
    workFlowDetailRuncommand.Parameters.Add("@2", SqlDbType.VarChar).Value <- "answer"
    workFlowDetailRuncommand.Parameters.Add("@3", SqlDbType.VarChar).Value <- mydate.ToString("yyyy.MM.dd")
    workFlowDetailRuncommand.Parameters.Add("@4", SqlDbType.VarChar).Value <- "D. Adams"
    workFlowDetailRuncommand.Parameters.Add("@5", SqlDbType.DateTime).Value <- DateTime.Now
    workFlowDetailRuncommand.Parameters.Add("@6", SqlDbType.Text).Value <- filename

Есть ли более идоматический способ сделать это (с меньшим количеством ввода!), Без установки параметров по одному, как это.


person user1443098    schedule 15.03.2017    source источник
comment
Взгляните на SqlCommandProvider здесь. Может не совсем соответствовать вашему варианту использования (особенно в случае динамического запроса), но хорошо обрабатывает параметры: fsprojects.github.io/FSharp.Data.SqlClient   -  person Honza Brestan    schedule 15.03.2017


Ответы (2)


Я думаю, что ответ Бента дает вам очень хороший DSL для создания стандартных объектов SqlCommand. Это вполне может быть именно то, что вам нужно — если вам просто нужен более приятный синтаксис для создания пары команд, он будет работать отлично.

Если вы хотите сделать больше с вашими SQL-командами, то у DSL есть одно ограничение, которое заключается в том, что он все еще основан на базовом изменяемом типе SqlCommand — он выглядит функциональным, но он мутирует объекты под прикрытием, что может привести вас в замешательство. беда.

Более комплексным вариантом было бы определение собственных функциональных типов для захвата домена, то есть типов запросов, которые вы хотите выполнять:

type Parameter =
  | Int of int
  | VarChar of string
  | Text of string
  | DateTime of System.DateTime

type Command = 
  { Query : string 
    Timeout : int
    Parameters : (string * Parameter) list }

Затем вы можете создавать запросы, используя обычные типы F # (и вы даже можете реализовать DSL, подобный тому, который Бент предложил поверх этого, сохраняя при этом неизменяемость):

let cmd = 
  { Query = query
    Timeout = 100000 
    Parameters = 
      [ "@1", Int 42
        "@2", VarChar "answer"
        "@3", VarChar (mydate.ToString("yyyy.MM.dd"))
        "@4", VarChar "D. Adams"
        "@5", DateTime DateTime.Now
        "@6", Text filename ] }

Последним битом было бы написать функцию, которая принимает команду и соединение и превращает их в SqlCommand:

let createSqlCommand cmd connection = 
  let sql = new SqlCommand(cmd.Query, connection) 
  sql.CommandTimeout <- cmd.Timeout
  for name, par in cmd.Parameters do
    let sqlTyp, value = 
      match par with
      | Int n -> SqlDbType.Int, box n
      | VarChar s -> SqlDbType.VarChar, box s
      | Text s -> SqlDbType.Text, box s
      | DateTime dt -> SqlDbType.DateTime, box dt
    sql.Parameters.Add(name, sqlTyp).Value <- value
  sql 

Наилучший подход будет зависеть от вашего варианта использования, а взаимодействие с базами данных по своей сути нечисто, поэтому, возможно, держать вещи изолированными и нечистыми вполне нормально. Хотя я хотел показать это как возможный вариант, если вы хотите быть более функциональным и сосредоточиться на предметной области (используя мощную сторону моделирования на основе предметной области F#!).

person Tomas Petricek    schedule 15.03.2017
comment
Я использовал эту технику для построения огромных объемов XML на основе типов F#, и она прекрасно сработала. - person Bent Tranberg; 16.03.2017
comment
Это потрясающе! Я начинаю использовать его. один уточняющий вопрос. Поскольку SQLConnection поддерживает Dispose, я делаю use connection = new SqlConnection(connString) Как мне вписать это в эту идею? - person user1443098; 16.03.2017
comment
@user1443098 user1443098 Вот почему я не включил SqlConnection в тип Command - таким образом, вы можете настроить команду независимо, а затем открыть соединение с помощью use, вызвать createSqlCommand и запустить команду SQL, а затем закрыть соединение. Есть еще императивная часть, но она доведена до самого конца. - person Tomas Petricek; 16.03.2017

Я не тестировал это.

Создайте несколько вспомогательных функций.

let createSqlCommand query connection =
    new SqlCommand(query, connection)

let setTimeout timeout (sqlCommand: SqlCommand) =
    sqlCommand.CommandTimeout <- timeout
    sqlCommand

let addInt name (value: int) (sqlCommand: SqlCommand) =
    sqlCommand.Parameters.Add(name, SqlDbType.Int).Value <- value
    sqlCommand

Используйте их так.

let mySqlCommand =
    createSqlCommand someQuery someConnection
    |> setTimeout 100000
    |> addInt "@1" 41
    |> addString "@s" "Hello"
    |> addDateTime "@dt1" DateTime.Now
    |> addFloat "@f1" 5.1

Если у вас есть int, имеет смысл всегда использовать SqlDbType.Int.

Но если у вас есть строка, есть несколько очевидных кандидатов на типы полей. По этой причине может быть хорошей идеей, чтобы имена функций addXxx отражали типы полей, а не типы F#/.NET. Что бы вы создавали addVarChar, addNVarChar, addChar и т.д.

|> addInt "@i1" myInt
|> addDateTime "@dt1" myDateTime
|> addText "@tagText" myTagText
|> addNVarChar "@letter" myLetterBody
person Bent Tranberg    schedule 15.03.2017