【问题标题】:F# SqlCommand Output ParametersF# SqlCommand 输出参数
【发布时间】:2017-03-29 02:37:48
【问题描述】:

F# best way to set up a SQLCommand with parameters

为构造 SQLCommand 输入参数提供了一些非常简洁的解决方案。现在我需要做一些输出参数来调用一个返回两个输出参数的存储过程。

到目前为止我有:

        let cmd = (createSqlCommand query conn)

        let pec = (new SqlParameter("@errorCode", SqlDbType.Int))
        pec.Direction <- ParameterDirection.Output
        ignore (cmd.Parameters.Add(pec))

        let pet = new SqlParameter("@errorMessage", SqlDbType.VarChar, 2000)
        pet.Direction <- ParameterDirection.Output
        ignore (cmd.Parameters.Add(pet))

        let rc = cmd.ExecuteNonQuery()
        let errorCode = cmd.Parameters.Item("@errorCode").Value.ToString()
        let errorText = cmd.Parameters.Item("@errorMessage").Value.ToString()

这行得通,但我觉得它丑陋且过于迫切。如何扩展前面示例中的解决方案(尤其是我现在正在使用的 Tomas)来处理输出参数?所以输入和输出在同一个命令中发出。

所以我尝试了这个:

type Command = 
  { Query : string 
    Timeout : int
    Parameters : (string * Parameter) list 
    OutParameters : Option<(string * OutParameter)> list}

接下来是:

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
    match cmd.OutParameters with
    | Some <string * OutParameter> list ->
        for name, par in list do
            let sqlParameter =
              match par with
              | OutInt -> new SqlParameter(name, SqlDbType.Int)
              | OutVarChar len -> new SqlParameter(name, SqlDbType.VarChar, len)
            sqlParameter.Direction <- ParameterDirection.Output
            sql.Parameters.Add sqlParameter |> ignore
    | _ -> ()

但我无法计算出接近尾声的匹配语法。我试过了:

一些列表 -> 得到了

错误 52 此表达式应具有类型 选项列表,但这里有类型 '一个选项

然后我尝试了:

| Some Option<string * OutParameter> list ->

遇到同样的错误,所以我尝试了:

| Some <string * OutParameter> list ->

得到一个不同的错误:

错误 53 模式中有意外的标识符。预期的中缀运算符, 引用符号或其他标记。

然后尝试:

| Some <(string * OutParameter)> list ->

得到错误:

错误 53 模式中出现意外符号 '('。预期中缀运算符, 引用符号或其他标记。

终于试过了:

| Some (string * OutParameter) list ->

又遇到了第一个错误。

然后,我放弃了。

这里需要什么语法?

想了一个新的:

| Some list : (string * OutParameter) ->
        for name, par in list do

但是“for”上的错误

错误 53 类型中存在意外的关键字“for”

新尝试:

我想也许我可以定义一个函数来构建一个期望输出参数的 sql 命令,并且仍然使用第一个 createSqlCommand 函数。我试过这个:

type OutCommand = 
  { Query : string 
    Timeout : int
    Parameters : (string * Parameter) list 
    OutParameters : (string * OutParameter) list 
  }

let createSqlCommandOut (cmd : OutCommand) connection = 
  let sql = createSqlCommand {cmd.Query; cmd.Timeout; cmd.Parameters} connection
  for name, par in cmd.OutParameters do
    let sqlParameter = 
        match par with
        | OutInt -> new SqlParameter(name, SqlDbType.Int)
        | OutVarChar len -> new SqlParameter(name, SqlDbType.VarChar, len)
    sqlParameter.Direction <- ParameterDirection.Output
    sql.Parameters.Add sqlParameter |> ignore
  sql 

这个想法是抓取传入的参数并将它们发送给原始函数来完成工作。你可能猜到这不起作用。我得到了错误;

错误 53 无效的对象、序列或记录表达式

在新函数中调用 createSqlCommand。这种事情可能吗?我可以使用 OutCommand 记录的成员制作命令记录吗?如果是这样,我该如何进行铸造? (似乎也不是一个上头下垂的人)

【问题讨论】:

    标签: f# sqlcommand


    【解决方案1】:

    托马斯当然更有资格回答这个问题,但我会试一试。如果他确实回答了,看看我是否走在正确的轨道上会很有趣。我想我有点不对劲。

    如果这运行得不太好,请耐心等待,因为我不会对其进行测试。我将基于the code Tomas gave us

    我认为我们需要一个新的 OutParameter 类型。

    type OutParameter =
      | OutInt
      | OutVarChar of int // the length is needed?
    

    在命令类型中,我们添加了一个名为 OutParameters 的额外字段。

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

    在cmd函数中,必须加上这个。

    OutParameters = 
      [ "@errorCode", OutInt
        "@errorMessage", OutVarChar 2000 ]
    

    函数 createSqlCommand 现在还必须处理 OutParameters。最后一个for循环是这里唯一的修改。

    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
      for name, par in cmd.OutParameters do
        let sqlParameter =
          match par with
          | OutInt -> new SqlParameter(name, SqlDbType.Int)
          | OutVarChar len -> new SqlParameter(name, SqlDbType.VarChar, len)
        sqlParameter.Direction <- ParameterDirection.Output
        sql.Parameters.Add sqlParameter |> ignore
      sql
    

    运行ExecuteNonQuery 后,您可以再次利用 OutParameters 列表来解析输出。

    现在是一个提取值的函数。

    let extractOutParameters (cmd: SqlCommand) (outParms: (string * OutParameter) list) =
      outParms
      |> List.map (fun (name, outType) ->
        match outType with
        | OutInt -> cmd.Parameters.Item(name).Value :?> int |> Int
        | OutVarChar _ -> cmd.Parameters.Item(name).Value.ToString() |> VarChar
      )
    

    我完全不确定像这样转换值是好的,你可能应该匹配类型,以正确处理错误。测试一下。但这是一个小问题,与我要演示的内容没有太大关系。

    请注意,此函数使用 Parameter 类型返回值,而不是 OutParameter 类型。此时我会考虑更改一种或两种类型的名称,以更好地反映它们的用途。

    更新

    您可以使用它来为命令和查询创建特定的函数。这是一个略带伪代码的 F# sn-p。

    type UserInfo = { UserName: string; Name: string; LastLogin: DateTime }
    
    let getUserInfo con userName : UserInfo =
        let cmd = {
            Query = "some sql to get the data"
            Timeout = 1000
            Parameters = ... the user name here
            OutParameters = ... the userName, Name and LastLogin here
            }
        let sqlCommand = createSqlCommand cmd con
        ... run the ExecuteNonQuery or whatever here
        let outs = extractOutParameters sqlCommand cmd.OutParameters
        {
            UserName = getValOfParam outs "@userName"
            Name = getValOfParam outs "@name"
            LastLogin = getValOfParam outs "@lastLogin"
        }
    

    您必须创建函数 getValOfParam,它只搜索具有正确名称的参数并返回其值。

    然后您可以像这样使用 getUserInfo。

    let userInfo = getUserInfo con "john_smith"
    

    即使返回了 10 个字段,您也会将它们放在一条记录中,因此很容易忽略您不想要的字段。

    如果你在调用它时构建了另一个结果你根本不感兴趣的函数,你会这样调用它。

    startEngineAndGetStatus con "mainEngine" |> ignore
    

    【讨论】:

    • 我对为什么必须提供 2000 的长度感到有些困惑,但只是理所当然地认为需要它。如果不是,那么显然可以进行简化。无论如何,在其他情况下,可能也需要传递有关如何解析输出数据的信息,因此这说明了这一点。
    • 我认为必须为客户端提供长度以分配存储以接收结果。否则它不知道查询返回什么。当然,我可以看中并使用 sp_describe_first_result_set 运行 proc 以找到实际长度。不过,这对这个程序来说太过分了。你在这里失去了我,“使用参数类型返回值,而不是 OutParameter”。 OutParameter 类型会不起作用吗?此外,在查询结束时,我需要将输出放在两个变量中而不是列表中。拆包容易吗?
    • 好的。我得到了 Parameters vs OutParameters 评论。花了我一分钟
    • 另一件事是,我可以将 OutParameters 设为可选吗?看起来怎么样?
    • 创建一个函数,该函数接受 OutParameters 并提供带有字段 ErrorCode 和 ErrorMessage 的结果记录。至于选项(双关语),您可以通过各种方式使用选项类型/关键字。更好的是,看看 Railway oriented programming,尤其是 Result 类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-06
    • 1970-01-01
    • 2011-04-13
    • 1970-01-01
    • 1970-01-01
    • 2011-10-12
    • 1970-01-01
    相关资源
    最近更新 更多