Trying to understand how to parameterize query with SqlCommandProvider. So far I've got this
[<Literal>]
let connectionString = #"Data Source=.\SQL2019;Initial Catalog=MyDb;Trusted_Connection=True;"
type InsertCommand =
SqlCommandProvider<"INSERT INTO Category (Name, Value, Website) VALUES (#name, #value, #website)", connectionString, SingleRow = true>
let cmdInsert = new InsertCommand()
let insertCategories (data: seq<string * string>) =
data
|> Seq.iter
(
fun x ->
cmdInsert.Execute(name = fst x, value = Int32.Parse(snd x), website = "www.example.com") |> ignore
)
But on the line new InsertCommand() I'm getting this error
The member or object constructor 'SqlCommandProvider,CommandText="...",ConnectionStringOrName="...",SingleRow="True"'
does not take 0 argument(s). An overload was found taking 2 arguments
Can someone please explain? If not, can someone please give an example on how to do parameterized insert query?
The error message is telling you that the you are missing an argument for the constructor of the provided type, i.e. your InsertCommand type. I do not have a SQL database to try this, but the type provided by SqlCommandProvider should have an overload that takes a connection string and (optionally) a timeout.
The following should do the trick:
let cmdInsert = new InsertCommand(connectionString)
Related
Getting into the bowels now. Hopefully this is the last time I have to deal with Reflection for a while and I can return to the high level where I belong.
I have a type "PrimaryKey" defined as such.
type PrimaryKey<'x> =
| Id of int
| EmptyPrimaryKey
Then a bunch of record types associated with database tables, for example:
type User = {
user_id: PrimaryKey<User>
username: string
email: string
address_id: PrimaryKey<Address>
} with
static member DatabaseTable = "users"
I have written a custom type handler for Dapper to handle the Primary Key type.
type PrimaryKeyHandler<'X>() =
inherit SqlMapper.TypeHandler<PrimaryKey<'X>>()
(* I don't think this works but that's a future problem *)
override _.SetValue(param, value) =
let valueOrNull =
match value with
| PrimaryKey.Id id -> box id
| EmptyPrimaryKey -> null
param.Value <- valueOrNull
override _.Parse value =
if isNull value || value = box DBNull.Value
then EmptyPrimaryKey
else Id (value :?> int)
Now the problem with that is I have to call::
SqlMapper.AddTypeHandler (PrimaryKeyHandler<User>())
SqlMapper.AddTypeHandler (PrimaryKeyHandler<OtherRecordType>())
On every record that has a primary key. I'm basing my solution off of this: Dapper generic typehandler for F# Union types
But I don't understand it enough to adapt it to my needs, I think I need extra handling for the generic type.
What I've started with is this:
let RegisterTypeHandlers () =
let assembly = Assembly.GetCallingAssembly()
let handler = typedefof<PrimaryKeyHandler<_>>
assembly.GetTypes()
|> Seq.filter(fun t ->
FSharpType.IsRecord(t) && t.GetProperty("DatabaseTable") <> null
)
Which successfully returns a list of record types which have database table associations.
However trying to iterate over that list and call AddTypeHandler on all of those types fails:
let RegisterTypeHandlers () =
let assembly = Assembly.GetCallingAssembly()
let handler = typedefof<PrimaryKeyHandler<_>>
assembly.GetTypes()
|> Seq.filter(fun t ->
FSharpType.IsRecord(t) && t.GetProperty("DatabaseTable") <> null
)
|> Seq.iter(fun t ->
printfn $"Type: {t.Name}"
let ctor = handler
.MakeGenericType(t)
.GetConstructor(Array.empty)
.Invoke(Array.empty)
(typeof<SqlMapper>.GetMethods()
|> Seq.filter(fun methodInfo ->
if methodInfo.Name = "AddTypeHandler" && methodInfo.IsGenericMethodDefinition then
let gp = methodInfo.GetParameters()
not <| isNull gp && gp.Length = 1 && gp.[0].ParameterType.Name.Contains("TypeHandler")
else false)
|> Seq.head)
.MakeGenericMethod(t)
.Invoke(null, [| ctor |]) |> ignore
)
The error being
Unhandled exception. System.ArgumentException: Object of type 'MyModule.Common+PrimaryKeyHandler`1[Program+User]' cannot be converted to type 'Dapper.SqlMapper+TypeHandler`1[Program+User]'.
I've been looking at some of the GenericType functions in reflection but not really sure where to go from here.
First of all, you are getting an error saying that PrimaryKeyHandler<User> cannot be converted to type TypeHandler<User>. This is correct, because your type PrimaryKeyHandler<User> inherits from TypeHandler<PrimaryKey<User>>.
I think this happens because you get the AddTypeHandler method via reflection and then use MakeGenericMethod(t) to make it generic - but if t is User, then you get the wrong generic instantiation - you need to wrap t with PrimaryKey<..> around it first.
I have not tested this, but I think the following should work:
let addTyMi =
typeof<SqlMapper>.GetMethods()
|> Seq.find(fun methodInfo ->
if methodInfo.Name = "AddTypeHandler" &&
methodInfo.IsGenericMethodDefinition then
let gp = methodInfo.GetParameters()
not <| isNull gp && gp.Length = 1 &&
gp.[0].ParameterType.Name.Contains("TypeHandler")
else false)
let pkt = typedefof<PrimaryKey<_>>.MakeGenericType(t)
addTyMi.MakeGenericMethod(pkt).Invoke(null, [| ctor |]) |> ignore
It seems to me that there is also a non-generic overload of AddTypeHandler taking System.Type (by browsing GitHub source - I have not tried this). Maybe you could do just:
let pkt = typedefof<PrimaryKey<_>>.MakeGenericType(t)
SqlMapper.AddTypeHandler(pkt, ctor)
...avoiding some of the reflection. Also, ctor is a bad name, because the variable refers to the instance!
Please consider this dataset, composed by man and woman, and that I filter in a second moment according to few variables:
type ls = JsonProvider<"...">
let dt = ls.GetSamples()
let dt2 =
dt |> Seq.filter (fun c -> c.Sex = "male" && c.Height > Some 150)
dt2
[{"sex":"male","height":180,"weight":85},
{"sex":"male","height":160" "weight":60},
{"sex":"male","height":180,"weight":85}]
Lets suppose that I would like to add a fourth key "body mass index" or "bmi", and that its value is roughly given by "weight"/"height". Hence I expect:
[{"sex":"male","height":180,"weight":85, "bmi":(180/85)},
{"sex":"male","height":160" "weight":60, "bmi":(160/60},
{"sex":"male","height":180,"weight":85, "bmi":(180/85)}]
I thought that map.Add may help.
let dt3 = dt2.Add("bmi", (dt2.Height/dt2.Weight))
Unfortunately, it returns an error:
error FS0039: The field, constructor or member 'Add' is not defined
I am sure there are further errors in my code, but without this function I cannot actually look for them. Am I, at least, approaching the problem correctly?
Creating modified versions of the JSON is sadly one thing that the F# Data type provider does not make particularly easy. What makes that hard is the fact that we can infer the type from the source JSON, but we cannot "predict" what kind of fields people might want to add.
To do this, you'll need to access the underlying representation of the JSON value and operate on that. For example:
type ls = JsonProvider<"""
[{"sex":"male","height":180,"weight":85},
{"sex":"male","height":160,"weight":60},
{"sex":"male","height":180,"weight":85}]""">
let dt = ls.GetSamples()
let newJson =
dt
|> Array.map (fun recd ->
// To do the calculation, you can access the fields via inferred types
let bmi = float recd.Height / float recd.Weight
// But now we need to look at the underlying value, check that it is
// a record and extract the properties, which is an array of key-value pairs
match recd.JsonValue with
| JsonValue.Record props ->
// Append the new property to the existing properties & re-create record
Array.append [| "bmi", JsonValue.Float bmi |] props
|> JsonValue.Record
| _ -> failwith "Unexpected format" )
// Re-create a new JSON array and format it as JSON
JsonValue.Array(newJson).ToString()
One of the benefits of the type system on F# is avoid a null exception... or that was something I believe... because I'm getting a null problem:
[<CLIMutable>]
type Customer = {
[<AutoIncrement>] id:option<int64>
code:string
name:string
}
I'm running a SQL code:
let SqlFTS<'T>(table:string, searchTable:string, query:string) =
use db = openDb()
let sql = sprintf "SELECT * FROM %s WHERE id in (SELECT docid FROM %s WHERE data MATCH %A)" table searchTable query
printfn "%A" sql
db.SqlList<'T>(sql) |> Seq.toArray
testCase "Customers" <|
fun _ ->
let rows = GenData.genCustomers(50)
Customers.insert(rows)
isEqual "Failed to insert" 50L (DB.SqlCount<Customers.Customer>())
//Until here, it works
let c = Customers.byId(1L)
printfn "%A" c
//Customers.byId return me a record with all the properties as NULLS!
//Then c.name is null, and the code above fail.
let rows = Customers.searchCustomers(c.name)
This was very unexpected. Why I can get a record with all values to null?
Let's start with line 1:
[<CLIMutable>]
The docs for CLIMutable state
Adding this attribute to a record type causes it to be compiled to a Common Language Infrastructure (CLI) representation with a default constructor with property getters and setters.
That default constructor means the fields will be initialized to default values. The default value for string is null. That's valid to the CLR and it's valid to C# and VB.NET. You may not be able to call the default ctor from F#, but pretty much anyone else can.
Welcome to interop; it can be a pain.
I am trying to find all posts in RavenDB containing a word (index is there)
Here is a query that works, finds everything that starts with 'Liv'
let post = query {
for post in session.Query<MyType>() do
where (post.Text.StartsWith("Liv"))
select post
}
An attempt to use string.Contains() method as condition of Where
closure, will throw NotSupportedException.
Here
So I am trying to use Search method where:
Expression<Func<T, object>> fieldSelector,
// Expression marking a field in which terms should be looked for.
C# equivalent from docs:
List<User> users = session
.Query<User>("Users/ByNameAndHobbies")
.Search(x => x.Name, "Adam")
.Search(x => x.Hobbies, "sport")
.ToList();
My first try was to go with
let x = session.Query<MyType>(index).Search((fun xe -> xe.Text ), "Liv")
But getting error because it expects object out. Tried to downcast String to Object (what a strange idea), but getting:
Cannot understand how to translate x => x.Invoke(xe)
At the moment, I am out of ideas. I am supposed to mark field for search and return object. Any ideas?
Thank you.
EDIT 1:
My expression. Gets runtime InvalidCastException because it can't cast string to obj.
let expr =
<# Func<MyType, _>(fun xe -> xe.Text ) #>
|> LeafExpressionConverter.QuotationToExpression
|> unbox<System.Linq.Expressions.Expression<Func<MyType, _>>>
You mentioned that you tried casting the string to object. I tried it using :> obj and it does work.
Here is my working query:
let expr = <# Func<MyType,_>(fun x -> x.Text :> obj ) #>
|> LeafExpressionConverter.QuotationToExpression
|> unbox<Linq.Expressions.Expression<Func<MyType,_>>>
let events = session.Query<MyType>()
.Search(expr, "Liv*", decimal 1, SearchOptions.Or, EscapeQueryOptions.AllowAllWildcards)
|> List.ofSeq
I'm using the FSharp.Data.JsonProvider to read Twitter Tweets.
Playing with this sample code
https://github.com/tpetricek/Documents/tree/master/Samples/Twitter.API
I want to expand the urls in the tweet with
let expandUrl (txt:string) (url:Search.DomainTypes<...>.DomainTypes.Url) =
txt.Replace( url.Url, url.ExpandedUrl )
This results in Error:
Lookup on object of indeterminate type based on information prior to this program point.
A type annotation may be needed prior to this program point to constrain the type of the object.
My problem is how to define the TypeProvider Type for url in the expandUrl function above?
The type inferance shows me this
val urls : FSharp.Data.JsonProvider<...>.DomainTypes.Url []
but this is not accepted in the type declaration. I assume "<...>" is not F# synatx.
How to do a type annotation for using a TypeProvider type e.g. FSharp.Data.JsonProvider<...>.DomainTypes.Url ?
Here is the complete code snippet:
open TwitterAPI // github.com/tpetricek/Documents/tree/master/Samples/Twitter.API
let twitter = TwitterAPI.TwitterContext( _consumerKey, _consumerSecret, _accessToken, _accessTokenSecret )
let query = "water"
let ts = Twitter.Search.Tweets(twitter, Utils.urlEncode query, count=100)
let ret =
[ for x in ts.Statuses do
// val urls : FSharp.Data.JsonProvider<...>.DomainTypes.Url []
let urls = x.Entities.Urls
// fully declarated to help the type inference at expandUrl
let replace (txt:string) (oldValue:string) (newValue:string) =
txt.Replace( oldValue, newValue)
// Error:
// Lookup on object of indeterminate type based on information prior to this program point.
// A type annotation may be needed prior to this program point to constrain the type of the object.
// This may allow the lookup to be resolved.
let expandUrl (txt:string) (url:FSharp.Data.JsonProvider<_>.DomainTypes.Url) =
replace txt url.Url url.ExpandedUrl
let textWithExpandedUrls = Array.fold expandUrl x.Text urls
yield textWithExpandedUrls
]
When you call Twitter.Search.Tweets (https://github.com/tpetricek/Documents/blob/master/Samples/Twitter.API/Twitter.fs#L284), the return type of that is one of the domain types of TwitterTypes.SearchTweets, which is a type alias for JsonProvider<"references\\search_tweets.json"> (https://github.com/tpetricek/Documents/blob/master/Samples/Twitter.API/Twitter.fs#L183).
Although in the tooltip it shows up as JsonProvider<...>.DomainTypes.Url, you'll have to use the type alias TwitterTypes.SearchTweets.DomainTypes.Url
I had a similar problem trying to figure out how to use the FSharp.Data HtmlProvider.
I am using Wikipedia to get information about USA presidents. The HtmlProvider does a great job of discovering the various tables in that webpage, but I wanted to extract the logic for processing a row of "president data" into a separate function called processRow.
And the problem was trying to work out what the type of such a row is for processRow's parameter row. The following code does the trick:
#load "Scripts\load-references.fsx"
open FSharp.Data
let presidents = new HtmlProvider<"https://en.wikipedia.org/wiki/List_of_Presidents_of_the_United_States">()
let ps = presidents.Tables.``List of presidents``
ps.Headers |> Option.map (fun hs -> for h in hs do printf "%s " h)
printfn ""
type Presidents = ``HtmlProvider,Sample="https://en.wikipedia.org/wiki/List_of_Presidents_of_the_United_States"``.ListOfPresidents
let processRow (row:Presidents.Row) =
printfn "%d %s" row.``№`` row.President2
ps.Rows |> Seq.iter processRow
I did not type in the long type alias for Presidents, I used Visual Studio auto-completion by guessing that the type for List of presidents would be discoverable from something starting with Html, and it was, complete with the four single back quotes.