Populate list with Types - f#

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

Related

F# SqlProvider fails to update changes in a dBase DBF file with ODBC connection

I have the following F# code
open FSharp.Data.Sql
open FSharp.Data.Sql.Runtime
open System.IO
[<Literal>]
let private schemaConn = #"Driver={Microsoft dBASE Driver (*.dbf)};DriverID=277;Dbq=C:\Citect\User\NPM;"
type private schema = SqlDataProvider<Common.DatabaseProviderTypes.ODBC, schemaConn>
let private connStringFormat = Printf.StringFormat<string->string>(#"Driver={Microsoft dBASE Driver (*.dbf)};DriverID=277;Dbq=%s;")
type internal Project = {
name : string
path : string
dcx : schema.dataContext
}
[<Literal>]
let private cUserPath = #"C:\Citect\User"
let private findPath projectName =
Directory.GetDirectories(cUserPath, projectName, SearchOption.AllDirectories)
|> Array.find (fun d -> d.Contains("web") |> not)
let internal connect projectName =
let path' = findPath projectName
let connString = sprintf connStringFormat path'
let dcx' = schema.GetDataContext(connString)
{ name = projectName; path = path'; dcx = dcx' }
let internal updVariable (project : Project) variable =
let dcx = project.dcx
let q = query {
for v in dcx.Dbo.Variable do
where (v.Addr = "%MW217.0")
select v
exactlyOne
}
q.Addr <- "QQQ"
dcx.SubmitUpdates() //error
let internal prj = connect "NPMUG_SCC35"
updVariable prj ()
Connection and query work as expected, but when I try to update the data source I get the following error coming from the odbc driver:
Message -> ERROR [HY092] [Microsoft][ODBC dBase Driver]Invalid
attribute/option identifier Source -> odbcjt32.dll
Is there a way to get it working or do I need to give up the type provider and resort back to OleDb?
UPDATE
Disabling transactions makes things a little better, now the error is due to the missing primary key in the dbf files I have to work with.
The only code changed is getting the data context
let dcx = schema.GetDataContext( { Timeout = TimeSpan.MaxValue; IsolationLevel = Transactions.IsolationLevel.DontCreateTransaction } : FSharp.Data.Sql.Transactions.TransactionOptions)
And the new error is:
System.Exception: Error - you cannot update an entity that does not
have a primary key. (dbo.variable) at
FSharp.Data.Sql.Providers.OdbcProvider.createUpdateCommand(IDbConnection
con, StringBuilder sb, SqlEntity entity, FSharpList`1 changedColumns)
at .$Providers.Odbc.FSharp-Data-Sql-Common-ISqlProvider-ProcessUpdates#648-4.Invoke(SqlEntity
e) at
Microsoft.FSharp.Collections.SeqModule.Iterate[T](FSharpFunc2 action,
IEnumerable1 source) at
FSharp.Data.Sql.Providers.OdbcProvider.FSharp-Data-Sql-Common-ISqlProvider-ProcessUpdates(IDbConnection
con, ConcurrentDictionary2 entities, TransactionOptions
transactionOptions, FSharpOption1 timeout) at
.$SqlRuntime.DataContext.f#1-69(SqlDataContext
__, IDbConnection con, Unit unitVar0) at FSharp.Data.Sql.Runtime.SqlDataContext.FSharp-Data-Sql-Common-ISqlDataContext-SubmitPendingChanges()
Any idea on how to deal with this probem?
I found a tricky/dirty way that I would classify more as a workaround than a real solution, but it works in my case; so I am going to use it unless/until someone else suggests a conclusive one.
To get the type provider working I need to do 2 things not in the usual workflow:
The data context needs to be retrieved with transactions disabled
Before performing changing operations on a DBF, I create a primary
key on that DBF using a lower level SQL statement
Here the working code
[<Literal>]
let private schemaConn = #"Driver={Microsoft dBASE Driver (*.dbf)};DriverID=277;Dbq=C:\Citect\User\NPM;READONLY=FALSE"
type private schema = SqlDataProvider<Common.DatabaseProviderTypes.ODBC, schemaConn>
let private connStringFormat = Printf.StringFormat<string->string>(#"Driver={Microsoft dBASE Driver (*.dbf)};DriverID=277;Dbq=%s;READONLY=FALSE")
type internal Project = {
name : string
path : string
dcx : schema.dataContext
}
[<Literal>]
let private cUserPath = #"C:\Citect\User"
let private findPath projectName =
Directory.GetDirectories(cUserPath, projectName, SearchOption.AllDirectories)
|> Array.find (fun d -> d.Contains("web") |> not)
let private createPK (cn : IDbConnection) =
let cm = cn.CreateCommand()
cm.CommandText <- "ALTER TABLE Variable ADD PRIMARY KEY (Name)"
try
cn.Open()
cm.ExecuteNonQuery() |> ignore
finally cn.Close()
let internal connect projectName =
let path' = findPath projectName
let connString = sprintf connStringFormat path'
let transOptions = { Timeout = TimeSpan.FromSeconds(3.0); IsolationLevel = Transactions.IsolationLevel.DontCreateTransaction }
let dcx' = schema.GetDataContext(connectionString = connString, transactionOptions = transOptions)
dcx'.CreateConnection() |> createPK
{ name = projectName; path = path'; dcx = dcx' }
let internal updVariable (project : Project) variable =
let dcx = project.dcx
let q = query {
for v in dcx.Dbo.Variable do
where (v.Addr = "%MW217.0")
select v
exactlyOne
}
q.Addr <- "QQQ"
dcx.SubmitUpdates()
let internal prj = connect "NPMUG_SCC35"
updVariable prj ()

How can I pass a parameter to Sql.execReaderF in FsSql?

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.

How to create record in match pattern

I want to write an application that read ip address from xml file. The file looks like
<range>
<start>192.168.40.1</start>
<end>192.168.50.255</end>
<subnet>255.255.255.0</subnet>
<gateway>192.168.50.1</gateway>
</range>
I create an records type to save the ip address
type Scope = { Start: IPAddress; End: IPAddress; Subnetmask: IPAddress; Gateway: IPAddress }
I wrote a unit function, that output the ip's.
loc
|> Seq.iter (fun e -> match e.Name.LocalName with
|"start" -> printfn "Start %s" e.Value
|"end" -> printfn "End %s" e.Value
|"subnet" -> printfn "Subnet %s" e.Value
|"gateway" -> printfn "Gateway %s" e.Value
| _ -> ())
How can I return the scope records type instead of unit?
As mentioned in the comments, the XML type provider makes this a lot easier. You can just point it at a sample file, it will infer the structur and let you read the file easily:
type RangeFile = XmlProvider<"sample.xml">
let range = RangeFile.Load("file-you-want-to-read.xml")
let scope =
{ Start = IPAddress.Parse(range.Start)
End = IPAddress.Parse(range.End)
Subnetmask = IPAddress.Parse(range.Subnet)
Gateway = IPAddress.Parse(range.Gateway) }
That said, you can certainly implement this yourself too. The code you wrote is a good start - there is a number of ways to do this, but in any case, you'll need to do some lookup based on the local name of the element (to find start, end, etc.).
One option is to load all the properties into a dictionary:
let lookup =
loc
|> Seq.map (fun e -> e.Name.LocalName, IPAddress.Parse(e.Value)
|> dict
Now you have a lookup table that contains IPAddress for each of the keys, so you can create Scope value using just:
let scope =
{ Start = lookup.["start"]; End = lookup.["end"];
Subnetmask = lookup.["subnet"]; Gateway = lookup.["gateway"] }
That said, the nice thing about the XML type provider is that it removes the need to do lookup based on string values and so you are less likely to make mistakes caused by typos.

How to pass the type generated by F#'s SqlDataProvider as a parameter to function

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

Converting string to UTF8Type in FluentCassandra

I am working with FluentCassandra in F# and attempting to convert a string to a UTF8Type in order to use the ExecuteNonQuery method. Has anyone been successful doing this?
Thanks,
Tom
Thank you Jack P. and Daniel for pointing me in the right direction.
To provide more examples so others can benefit, I am writing a wrapper on top of FluentCassandra in F# to make CRUD functionality much simpler by utilizing the succinctness of F#. I am using Nick Berardi's code as an example for this wrapper:
https://github.com/fluentcassandra/fluentcassandra/blob/master/test/FluentCassandra.Sandbox/Program.cs
For example, if you want to check if a keyspace exists, simply calling the KeySpaceExists(keyspaceName) would allow for checking if a keyspace exists, using CreateKeyspace(keyspaceName) would allow for creation of a keyspace, etc. An example of the library I am creating is here:
namespace Test
open System
open System.Collections.Generic
open System.Configuration
open System.Linq
open System.Text
open System.Windows
open FluentCassandra
open FluentCassandra.Connections
open FluentCassandra.Types
open FluentCassandra.Linq
module Cassandra =
let GetAppSettings (key : string) = ConfigurationManager.AppSettings.Item(key)
let KeyspaceExists keyspaceName =
let server = new Server(GetAppSettings("Server"))
let db = new CassandraContext(keyspaceName, server)
let keyspaceNameExists = db.KeyspaceExists(keyspaceName)
db.Dispose()
keyspaceNameExists
let CreateKeyspace keyspaceName =
let server = new Server(GetAppSettings("Server"))
let db = new CassandraContext(keyspaceName, server)
let schema = new CassandraKeyspaceSchema(Name=keyspaceName)
let keyspace = new CassandraKeyspace(schema,db)
if KeyspaceExists(keyspaceName)=false then keyspace.TryCreateSelf()
db.Dispose()
let DropKeyspace (keyspaceName : string ) =
let server = new Server(GetAppSettings("Server"))
let db = new CassandraContext(keyspaceName, server)
match db.KeyspaceExists(keyspaceName)=true with
// value has "ignore" to ignore the string returned from FluentCassandra
| true -> db.DropKeyspace(keyspaceName) |> ignore
| _ -> ()
db.Dispose()
let ColumnFamilyExists (keyspaceName, columnFamilyName : string) =
let server = new Server(GetAppSettings("Server"))
let db = new CassandraContext(keyspaceName, server)
let schema = new CassandraKeyspaceSchema(Name=keyspaceName)
let keyspace = new CassandraKeyspace(schema,db)
let columnFamilyNameExists = db.ColumnFamilyExists(columnFamilyName)
db.Dispose()
columnFamilyNameExists
let CreateColumnFamily (keyspaceName, columnFamilyName: string) =
if ColumnFamilyExists(keyspaceName,columnFamilyName)=false then
let server = new Server(GetAppSettings("Server"))
let db = new CassandraContext(keyspaceName, server)
let schema = new CassandraKeyspaceSchema(Name=keyspaceName)
let keyspace = new CassandraKeyspace(schema,db)
if ColumnFamilyExists(keyspaceName,columnFamilyName)=false then
keyspace.TryCreateColumnFamily(new CassandraColumnFamilySchema(FamilyName = columnFamilyName, KeyValueType = CassandraType.AsciiType, ColumnNameType = CassandraType.IntegerType, DefaultColumnValueType = CassandraType.UTF8Type))
let ExecuteNonQuery(keyspaceName, query: string) =
let server = new Server(GetAppSettings("Server"))
let db = new CassandraContext(keyspaceName, server)
let schema = new CassandraKeyspaceSchema(Name=keyspaceName)
let keyspace = new CassandraKeyspace(schema,db)
let queryUTF8 = FluentCassandra.Types.UTF8Type.op_Implicit query
try
db.ExecuteNonQuery(queryUTF8)
true
with
| _ -> false
This library allows for very easy one line commands to utilize the FluentCassandra functionality. Of course this is just the start and I plan on amending the above library further.
open System
open System.Linq
open System.Collections.Generic
open System.Configuration
open FluentCassandra.Connections
open FluentCassandra.Types
open FluentCassandra.Linq
open Test.Cassandra
[<EntryPoint>]
let main argv =
CreateKeyspace("test1")
printfn "%s" (ColumnFamilyExists("test1", "table1").ToString())
printfn "%s" (KeyspaceExists("test1").ToString())
CreateColumnFamily("test1","table1")
printfn "%s" (ColumnFamilyExists("test1", "table1").ToString())
let result = ExecuteNonQuery("test1", "CREATE TABLE table2 (id bigint primary key, name varchar)")
printfn "%s" (result.ToString())
let wait = System.Console.ReadLine()
0
Specifically with converting the query string to a UTF8Type, Daniel's approach of utilizing UTF8Type.op_Implicit str worked. You can see how I applied it in the ExecuteNonQuery function above. Thanks again for your help!

Resources