It is not good form to create a query like
let fnam_query =
"select * from file_name_info where fnam_origin = 'invoice_cloud'"
But the code block below has two problems. First, the fnam_readOk returns false from the read.
Second, how can the OleDbParameter be disposed? I tried using use, but got a compile-time error saying OleDbType.Char could not be used within a use.
let fnam_query =
"select * from file_name_info where fnam_origin = '?' "
use fnam_cmd = new OleDbCommand(fnam_query, db_con)
let local_params = new OleDbParameter("fnam_origin", OleDbType.Char)
fnam_cmd.Parameters.Add(local_params) |> ignore
let fnam_reader = fnam_cmd.ExecuteReader ()
let fnam_readOK = fnam_reader.Read ()
let ic_lb_fnam =
if fnam_readOK then
fnam_reader.GetString(2)
else
"ic_lockbox.txt"
There are a lot of problems with this code. One of them, is that you used the OleDbParameter overload that passes a name and value. The line new OleDbParameter("fnam_origin", OleDbType.Char) specifies a parameter with the name fnam_origin and an integer value equal to whatever the underlying value of OleDbType.Char is.
The other problem is that you don't use that parameter at all. '?' is just a string that contains ?.
You don't need to quote parameters in a parameterized query. They aren't placeholders for string replacement. They specify actual, strongly typed parameters, just like an F# function parameter.
Your query should be :
let fnam_query =
"select * from file_name_info where fnam_origin = ? "
You should also use the correct parameter type. Char is used only for fixed-length parameters. You should use VarChar or even better, NVarchar.
Finally, you should pass the parameter value that you want. Your code doesn't specify a parameter value at all.
The entire function should look like this :
let fnam_query = "select * from file_name_info where fnam_origin = ? "
use db_con = new OleDbConnection("...")
use fnam_cmd = new OleDbCommand(fnam_query, db_con)
let local_params = new OleDbParameter("origin", SqlDbType.NVarChar,100)
fnam_cmd.Parameters.Add(local_params) |> ignore
local_params.Value <- "GR"
db_con.Open()
let fnam_reader = fnam_cmd.ExecuteReader ()
let fnam_readOK = fnam_reader.Read ()
...
A better implementation though, would be to create the command once and reuse it with different connections and values:
let build_cmd =
let fnam_query = "select * from file_name_info where fnam_origin = ? "
let fnam_cmd = new OleDbCommand(fnam_query)
let local_params = new OleDbParameter("whatever", SqlDbType.NVarChar,100)
fnam_cmd.Parameters.Add(local_params) |> ignore
fnam_cmd
use db_con = new OleDbConnection("...")
build_cmd.Connection <- db_con
build_cmd.Parameters.[0].Value <- "GR"
db_con.Open()
Based on the excellent answer I got from Panagiotis Kanavos, I've altered a different place in my code where I needed parameters that were not embedded in the query string. I chose to use cmd.Parameters.Add rather than have a separate OleDbParameter val.
(* by looking at the xfer_type, really the arg passed to main,
we can determine the report type parameter for the Access
database. *)
let select_report_type xfer_type =
match xfer_type with
| "/al" -> 0
| "/am" -> 1
| "/ap" -> 2
| "/pm" -> 3
| "/pp" -> 4
| _ -> 99
let query = "select count(*) from ProcessStatus where ReportType = ? and ReportDate = ? and ReportFileName = ? "
use cmd = new OleDbCommand(query , db_con)
cmd.Parameters.Add(new OleDbParameter("ReportType",(OleDbType.VarChar,5))) |> ignore
cmd.Parameters.[0].Value <- ((select_report_type xfer_type).ToString())
cmd.Parameters.Add(new OleDbParameter("ReportDate",OleDbType.VarChar, 11)) |> ignore
cmd.Parameters.[1].Value <- report_date
cmd.Parameters.Add(new OleDbParameter("ReportFileName",OleDbType.VarChar, 100)) |> ignore
cmd.Parameters.[2].Value <- fn
let sql_reader = cmd.ExecuteReader ()
if (sql_reader.Read ()) then
Related
In F# best way to set up a SQLCommand with parameters
some very neat solutions were given for constructing SQLCommand input parameters. Now I need to do some output parameters for calling a stored procedure that returns two output parameters.
So far I have:
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()
Which works, but I find it ugly and too imperative. How can I expand the solutions in the previous example, (especially Tomas, which I'm now using) to handle output parameters too? So input and output in the same command to be issued.
So I tried this:
type Command =
{ Query : string
Timeout : int
Parameters : (string * Parameter) list
OutParameters : Option<(string * OutParameter)> list}
followed by this:
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
| _ -> ()
But I can't work out the syntax for the match near the end. I tried:
Some list -> and got
Error 52 This expression was expected to have type
Option list but here has type
'a option
Then I tried:
| Some Option<string * OutParameter> list ->
got the same error, So I tried:
| Some <string * OutParameter> list ->
got a different error:
Error 53 Unexpected identifier in pattern. Expected infix operator,
quote symbol or other token.
Then tried:
| Some <(string * OutParameter)> list ->
got the error:
Error 53 Unexpected symbol '(' in pattern. Expected infix operator,
quote symbol or other token.
Finally tried:
| Some (string * OutParameter) list ->
and got the first error again.
Then, I gave up.
What syntax is needed here?
Thought up a new one:
| Some list : (string * OutParameter) ->
for name, par in list do
but that errors on "for"
Error 53 Unexpected keyword 'for' in type
New Attempt:
I thought maybe I could define a function to build a sql command expecting output parameters and still use the first createSqlCommand function. I tried this:
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
The idea is to grab the parameters passed in and send them on to the original function to do the work. You probably guessed that this doesn't work. I get the errors;
Error 53 Invalid object, sequence or record expression
On the call to createSqlCommand in the new function. Is this kind of thing possible? Can I make a Command record using the members of an OutCommand record? If so, how do I do the casting? (It seems to be neither an upcast downcast)
Tomas is of course much better qualified to answer this, but I'll give it a try. If he does answer, It'll be interesting to see if I'm on the right track. I guess I'm slightly off.
Bear with me if this doesn't quite run well, since I won't test it. I will base this on the code Tomas gave us.
I think we need a new OutParameter type.
type OutParameter =
| OutInt
| OutVarChar of int // the length is needed?
In the Command type we add an extra field named OutParameters.
type Command =
{ Query : string
Timeout : int
Parameters : (string * Parameter) list
OutParameters : (string * OutParameter) list }
In the cmd function, this must be added.
OutParameters =
[ "#errorCode", OutInt
"#errorMessage", OutVarChar 2000 ]
The function createSqlCommand must now also handle OutParameters. The last for-loop is the only modification here.
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
After you have run your ExecuteNonQuery, you can again take advantage of your list of OutParameters to parse the output.
Now a function to extract the values.
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
)
I am not at all sure that casting the values like this is good, and you probably should match on the type instead, to handle errors properly. Test it. But that's a minor issue not much related to what I'm trying to demonstrate.
Notice that this function uses the Parameter type for returning the values, rather than the OutParameter type. At this point I would consider changing the names of one or both types, to better reflect their use.
UPDATE
You can use this to create specific functions for commands and queries. Here is a slightly pseudo-codish F# snippet.
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"
}
You will have to create the function getValOfParam, which just searches outs for the parameter with the correct name, and returns its value.
You can then use getUserInfo like this.
let userInfo = getUserInfo con "john_smith"
Even if there were ten fields returned, you'd get them in one record, so it's simple to ignore the fields you don't want.
And if you had built another function with results you weren't interested in at all when calling it, you'd call it like this.
startEngineAndGetStatus con "mainEngine" |> ignore
Im trying to populate list with my own type.
let getUsers =
use connection = openConnection()
let getString = "select * from Accounts"
use sqlCommand = new SqlCommand(getString, connection)
try
let usersList = [||]
use reader = sqlCommand.ExecuteReader()
while reader.Read() do
let floresID = reader.GetString 0
let exName = reader.GetString 1
let exPass = reader.GetString 2
let user = [floresID=floresID; exName=exName; exPass=exPass]
// what here?
()
with
| :? SqlException as e -> printfn "Došlo k chybě úrovni připojení:\n %s" e.Message
| _ -> printfn "Neznámá výjimka."
In C# I would just add new object into userList. How can I add new user into list? Or is it better approach to get some sort of list with data from database?
Easiest way to do this is with a type provider, so you can abstract away the database. You can use SqlDataConnection for SQLServer, SqlProvider for everything (incl. SQLServer), and also SQLClient for SQLServer.
Here is an example with postgres's dvdrental (sample) database for SQLProvider:
#r #"..\packages\SQLProvider.1.0.33\lib\FSharp.Data.SqlProvider.dll"
#r #"..\packages\Npgsql.3.1.8\lib\net451\Npgsql.dll"
open System
open FSharp.Data.Sql
open Npgsql
open NpgsqlTypes
open System.Linq
open System.Xml
open System.IO
open System.Data
let [<Literal>] dbVendor = Common.DatabaseProviderTypes.POSTGRESQL
let [<Literal>] connString1 = #"Server=localhost;Database=dvdrental;User Id=postgres;Password=root"
let [<Literal>] resPath = #"C:\Users\userName\Documents\Visual Studio 2015\Projects\Postgre2\packages\Npgsql.3.1.8\lib\net451"
let [<Literal>] indivAmount = 1000
let [<Literal>] useOptTypes = true
//create the type for the database, based on the connection string, etc. parameters
type sql = SqlDataProvider<dbVendor,connString1,"",resPath,indivAmount,useOptTypes>
//set up the datacontext, ideally you would use `use` here :-)
let ctx = sql.GetDataContext()
let actorTbl = ctx.Public.Actor //alias the table
//set up the type, in this case Records:
type ActorName = {
firstName:string
lastName:string}
//extract the data with a query expression, this gives you type safety and intellisense over SQL (but also see the SqlClient type provider above):
let qry = query {
for row in actorTbl do
select ({firstName=row.FirstName;lastName=row.LastName})
}
//seq is lazy so do all kinds of transformations if necessary then manifest it into a list or array:
qry |> Seq.toArray
The two important parts are defining the Actor record, and then in the query extracting the fields into a sequence of Actor records. You can then manifest into a list or array if necessary.
But you can also stick to your original solution. In that case just wrap the .Read() into a seq:
First define the type:
type User = {
floresID: string
exName: string
exPass: string
}
Then extract the data:
let recs = cmd.ExecuteReader() // execute the SQL Command
//extract the users into a sequence of records:
let users =
seq {
while recs.Read() do
yield {floresID=recs.[0].ToString()
exName=recs.[1].ToString()
exPass=recs.[2].ToString()
}
} |> Seq.toArray
Taking your code, you can use list expression:
let getUsers =
use connection = openConnection()
let getString = "select * from Accounts"
use sqlCommand = new SqlCommand(getString, connection)
try
[
use reader = sqlCommand.ExecuteReader()
while reader.Read() do
let floresID = reader.GetString 0
let exName = reader.GetString 1
let exPass = reader.GetString 2
let user = [floresID=floresID; exName=exName; exPass=exPass]
yield user
]
with
| :? SqlException as e -> failwithf "Došlo k chybě úrovni připojení:\n %s" e.Message
| _ -> failwithf "Neznámá výjimka."
That being said, I'd use FSharp.Data.SqlClient library so all of that boiler plate becomes a single line with added benefit of type safety (if you change the query, the code will have compile time error which are obvious to fix).
I am currently using FSharp.Data.SqlClient, but I am interested in any solution of my task.
I have a web client and the backend written in F#. In the web client user can set 10-20 filters and make the GET request to my F# backend. The URL looks like:
http://mybackend.com/get/1/0/34/ ....
Any number in this URL is a filter. There is no filter on the corresponding field if the value of corresponding filter is zero.
Now I need to construct the SQL query in my backend. While the number of possible filters was 3-4 I was using pattern matching:
type GetEObyOrg1Org2AsutpParent = SqlCommandProvider<"SELECT * FROM ITEM WHERE ORGID1 = #org1 AND ORGID2 = #org2 AND ASUTPID = #asutp AND PARENTAUTOINCID = #parent", "name=MobileConnectionProvider", ResultType.Tuples>
type GetEObyOrg1Org2Org3AsutpParent = SqlCommandProvider<"SELECT * FROM ITEM WHERE ORGID1 = #org1 AND ORGID2 = #org2 AND ORGID3 = #org3 AND ASUTPID = #asutp AND PARENTAUTOINCID = #parent", "name=MobileConnectionProvider", ResultType.Tuples>
match (asutpid, orgId1, orgId2, orgId3) with
| "0", _, "0", "0" ->
let cmd = new GetEObyOrg1Org2AsutpParent()
cmd.Execute(org1 = orgId1, parent = parentAid)
| "0", _, _ , "0" ->
let cmd = new GetEObyOrg1Org2Org3AsutpParent()
cmd.Execute(org1 = orgId1, org2 = orgId2, parent = parentAid)
But when the number of filters is greater then 3-4 it is very difficult to write pattern matching for all combinations of the parameters.
I think I need to construct the SQL query dynamically. Each non-zero parameter in the URL must add AND Field = Value expression to SQL statement. But I can not do it:
type GetEObyOrg1AsutpParent = SqlCommandProvider<Query, "name=MobileConnectionProvider", ResultType.Tuples>
Query paramter is this expression must be Literal, and I can not to construct literal expression dinamically:
[<Literal>]
let q1 = "SELECT * FROM ITEM WHERE ORGID1 = #org1 AND ASUTPID = #asutp"
[<Literal>]
let q2 = " AND PARENTAUTOINCID = #parent"
let f a =
match a with
| 1 -> q1 + q2
| _ -> q1
[<Literal>]
let q3 = f()
What is the best way of doing it in my case ?
I think in a case like this the core library's SqlDataConnection type provider might be a better fit. Then you should be able to use standard IQueryable operations to dynamically add additional WHERE clauses, something like:
type MyDb = SqlDataConnection<...>
let items = MyDb.GetContext().Item
let q1 = if asutpid = 0 then items else items.Where(fun i -> i.Asutpid = asutpid)
let q2 = if orgId1 = 0 then q1 else q1.Where(fun i -> i.OrgId1 = orgid1)
...
I am trying out the samples for FsSql and I seem to be stuck on how to properly use the Sql.execReaderF function. The example code uses an int parameter but I have a string. The following code blocks show my attempts. Does FsSql only support int for this function maybe?
Setup code:
module FsSqlTests
open System
open System.Data
open System.Data.SqlClient
open NUnit.Framework
open Swensen.Unquote
let openConn() =
let conn = new SqlConnection(#"Data Source=MYSERVER;Initial Catalog=MYDB;Integrated Security=True")
conn.Open()
conn :> IDbConnection
let connMgr = Sql.withNewConnection openConn
let P = Sql.Parameter.make
let execReader sql = Sql.execReader connMgr sql
let execReaderf sql = Sql.execReaderF connMgr sql
Using Sql.execReader (Test case passes using this one)
let selectSummaryByeFolderName eFolderName =
execReader "select summary from ework.V_DQ_Iccm_Activity_By_Team WHERE efoldername = #eFolderName"
[P("#eFolderName", eFolderName)]
Using Sql.execReaderF (Test case fails using this one)
let selectSummaryByeFolderName =
execReaderf "select summary from ework.V_DQ_Iccm_Activity_By_Team WHERE efoldername = '%s'"
Calling code in the test case:
[<TestCase>]
let ``Gets CM summary given eFolderName``() =
let c = selectSummaryByeFolderName "CM008671"
let r = c
|> Seq.ofDataReader
|> Seq.map(fun dr ->
let s =
match dr?summary with
| None -> "No Summary"
| Some x -> x
s)
|> Seq.length
test <# r > 0 #>
How can I modify my call to execReaderF to make it pass the parameter and run correctly?
UPDATE:
I tried it out with an integer parameter and it works fine. It seems the function may only support integers.
let selectSummaryByCallPriority =
execReaderf "select top 10 summary from ework.V_DQ_Iccm_Activity_By_Team WHERE callpriority = %d"
I had a look at the implementation to try and verify this but it's over my head. Anyway the Sql.execReader function works fine for other datatypes so I can just switch to that function for my string parameters.
I'm try to write a tool that compares two db using F#'s SqlDataProvider as the data access. This means excuting the same query on two different databases. The would be easy, if I could pass the data content to a function as a parameter, however, because the data context is a generated type is doesn't seem to have proper name, so I'm unable to pass it as a parameter.
Here an example of what I'd like to able to do:
type MyDb = SqlDataProvider<
#"Server=myServerDatabase=myDatabase;Trusted_Connection=True;",
Common.DatabaseProviderTypes.MSSQLSERVER>
let ctx1 = RfqDb.GetDataContext("Server=myServerDatabase=myDatabase;Trusted_Connection=True;")
let ctx2 = RfqDb.GetDataContext("Server=myServerDatabase=myOtherDatabase;Trusted_Connection=True;")
let getGetData (ctx: ...) = // don't know what to put for ...
query { for ue in ctx.``[dbo].[MyTable]`` do
where (ue.UnderlyingID = "MyId")}
|> Seq.toArray
let grid1 = new EntityViewGrid()
let grid2 = new EntityViewGrid()
grid1.ItemsSource <- getGetData ctx1
grid2.ItemsSource <- getGetData ctx2
It's the line with the // don't know what to put for ... comment that's giving me problems.
Just figured it out, simpler than I thought, just the VS tooltips are a bit misleading. The correct sample looks like:
type MyDb = SqlDataProvider<
#"Server=myServerDatabase=myDatabase;Trusted_Connection=True;",
Common.DatabaseProviderTypes.MSSQLSERVER>
let ctx1 = RfqDb.GetDataContext("Server=myServerDatabase=myDatabase;Trusted_Connection=True;")
let ctx2 = RfqDb.GetDataContext("Server=myServerDatabase=myOtherDatabase;Trusted_Connection=True;")
let getGetData (ctx: MyDb.dataContext) =
query { for ue in ctx.``[dbo].[MyTable]`` do
where (ue.UnderlyingID = "MyId")}
|> Seq.toArray
let grid1 = new EntityViewGrid()
let grid2 = new EntityViewGrid()
grid1.ItemsSource <- getGetData ctx1
grid2.ItemsSource <- getGetData ctx2