We are trying to use dapper to return multiple result sets in F#. However I keep getting an Invalid operation Exception. This is what we have set up:
let query<'T, 'T1> connStr sql =
async {
use connection = new SqlConnection(connStr)
do! connection.OpenAsync() |> Async.AwaitTask
use transaction = connection.BeginTransaction()
try
use! result =
connection.QueryMultipleAsync(sql.Query, dict sql.Parameters, transaction)
|> Async.AwaitTask
transaction.Commit()
let r1 = result.Read<'T>()
let r2 = result.Read<'T1>()
return (r1, r2)|> Ok
with
| ex ->
transaction.Rollback()
return ex |> DataException |> Error
}
The expected result would be a tuple of seqs, but currently I just keep getting
System.InvalidOperationException: 'There is already an open DataReader associated with this Command which must be closed first.'
The issue appears to be related to calling transaction.commit before read. Here is the correct implementation for this method:
let query<'T, 'T1> connStr sql =
async {
use connection = new SqlConnection(connStr)
do! connection.OpenAsync() |> Async.AwaitTask
use transaction = connection.BeginTransaction()
try
use! result =
connection.QueryMultipleAsync(sql.Query, dict sql.Parameters, transaction)
|> Async.AwaitTask
let r1 = result.Read<'T>()
let r2 = result.Read<'T1>()
// Move the commit after the reads
// Was before orignally
transaction.Commit()
return (r1, r2)|> Ok
with
| ex ->
transaction.Rollback()
return ex |> DataException |> Error
}
Related
I need to request data from several URLs and then use the results.
I am using plain Fable 3 with the Fable-Promise and Fable-Fetch libraries.
I have worked out how to fetch from multiple URLs and combine the results into a single Promise that I can then use to update the UI (the multiple results need to be drawn only once).
But if one of the fetch errors then the whole thing falls over. Ideally I'd like to use tryFetch and then propagate the Result<TermData, None | Exception> but nothing I do seems to compile.
How exactly do I use tryFetch and then unwrap the result with a second let! in the CE? (The comments explain more)
module App
open Browser.Dom
open App
open System.Collections.Generic
open System.Text.RegularExpressions
open Fetch
open System
type TermData =
abstract counts : int []
abstract scores : int []
abstract term : string
abstract allWords : bool
type QueryTerm =
{ mutable Term: string
mutable AllWords: bool }
let loadSingleSeries (term: QueryTerm) =
promise {
let url =
$"/api/plot/{term.Term}?allWords={term.AllWords}"
// Works but doesn't handle errors.
let! plotData = fetch url [] // type of plotData: Response
// let plotDataResult = tryFetch url []
// This ideally becomes Promise<Result<TermData, None>>
// let unwrapped = match plotDataResult with
// | Ok res -> Ok (res.json<TermData>()) // type: Promise<TermData>
// | Error err -> ??? // tried Error (Promise.create(fun resolve reject -> resolve None)) among others
let! result = plotData.json<TermData>() // type of result: TermData
return result
}
let dataArrays =
parsed // type Dictionary<int, TermData>
|> Seq.map (fun term -> loadSingleSeries term.Value)
|> Promise.Parallel
|> Promise.map (fun allData -> console.log(allData))
// Here we will handle None when we have it
I don't have much Fable experience, but if I understand your question correctly, I think the following should work:
let loadSingleSeries (term: QueryTerm) =
promise {
let url =
$"/api/plot/{term.Term}?allWords={term.AllWords}"
let! plotDataResult = tryFetch url []
match plotDataResult with
| Ok resp ->
let! termData = resp.json<TermData>()
return Ok termData
| Error ex ->
return Error ex
}
The idea here is that if you get an error, you simply propagate that error in the new Result value. This returns a Promise<Result<TermData, Exception>>, as you requested.
Update: Fixed return type using a second let!.
I haven't run this code but looking at the docs it looks like you need to use Promise.catch
let loadSingleSeries (term: QueryTerm) =
promise {
let url =
$"/api/plot/{term.Term}?allWords={term.AllWords}"
let! plotDataResult =
fetch url []
|> Promise.map Ok // Wraps the "happy path" in a Result.Ok
|> Promise.catch (fun err ->
//handle the error
Error err)
return
match plotDataResult with
| Ok res -> ...
| Error err -> ...
}
I ended up having to use the pipeline rather than CE approach for this as follows:
let loadSingleSeries (term: QueryTerm) =
let url =
$"/api/plot/{term.Term}?allWords={term.AllWords}"
let resultPromise =
fetch url []
|> Promise.bind (fun response ->
let arr = response.json<TermData> ()
arr)
|> Promise.map (Ok)
|> Promise.catch (Error)
resultPromise
The key was using Promise.bind to convert the first promise to get the Response to the promise of Promise<TermData>. The map and catch then convert to a Promise<Result<TermData, exn>>.
Not really sure why my code fails to compile, the error I am having:
Incomplete structured construct at or before this point in expression
let resendErrorsAsync (bus: IBus) (errorQueueName: string) =
async {
let! errorQueue = bus.Advanced.QueueDeclareAsync(errorQueueName) |> Async.AwaitTask
let! message = bus.Advanced.GetMessageAsync(errorQueue) |> Async.AwaitTask
while message <> null do
let utf8Body = Encoding.UTF8.GetString(message.Body)
let error = JsonConvert.DeserializeObject<Error>(utf8Body)
let errorBodyBytes = Encoding.UTF8.GetBytes(error.Message)
let! exchange = bus.Advanced.ExchangeDeclareAsync(error.Exchange, "topic") |> Async.AwaitTask
let! message = bus.Advanced.GetMessageAsync(errorQueue) |> Async.AwaitTask
}
It seems to be related to my two async calls in the while loop, not really sure why though.
You can't end a block with a let!, there has to be a body after it; that's the reason for the syntax error.
The message you're defining on the last line is a different variable to the one tested in the while condition. It seems you want to mutate the message; for that, you have to explicitly create a mutable variable:
let resendErrorsAsync (bus: IBus) (errorQueueName: string) =
async {
let! errorQueue = bus.Advanced.QueueDeclareAsync(errorQueueName) |> Async.AwaitTask
let! msg = bus.Advanced.GetMessageAsync(errorQueue) |> Async.AwaitTask
let mutable message = msg
while message <> null do
let utf8Body = Encoding.UTF8.GetString(message.Body)
let error = JsonConvert.DeserializeObject<Error>(utf8Body)
let errorBodyBytes = Encoding.UTF8.GetBytes(error.Message)
let! exchange = bus.Advanced.ExchangeDeclareAsync(error.Exchange, "topic") |> Async.AwaitTask
let! msg = bus.Advanced.GetMessageAsync(errorQueue) |> Async.AwaitTask
message <- msg
}
So I currently have a sequence of type seq<System.Threading.Tasks.Task<Restaurant>> and I want to turn it into a sequence of type seq<Restaurant>.
I'm currently using TaskBuilder.fs library and from my research, I need to use either let! or do! for this situation but they require task {} which when used with Seq.map bring back the same Task type.
let joinWithReviews (r : Restaurant) =
task {
let! reviewResult = Reviews.Database.getByLocationId cnf.connectionString r.Restaurant_Id
match reviewResult with
| Ok reviewResult ->
let restaurant = { r with Reviews = (List.ofSeq reviewResult)}
return restaurant
| Error ex ->
return raise ex
}
let indexAction (ctx : HttpContext) =
task {
let (cnf:Config) = Controller.getConfig ctx
let! result = Restaurants.Database.getAll cnf.connectionString
match result with
| Ok result ->
let restaurantWithReviews = (Seq.map joinWithReviews result)
return index ctx (List.ofSeq restaurantWithReviews)
| Error ex ->
return raise ex
}
So my result is of type Seq<Restaurant> and I need to add reviews to each restaurant so I use Seq.map to get restaurantWithReviews which is type seq<System.Threading.Tasks.Task<Restaurant>> which I won't be able to use.
The .NET method System.Threading.Tasks.Task.WhenAll will convert seq<Task<'a>> to Task<'a[]>. You can get the result with let! if you're inside a task { } block.
let restaurants: seq<Restaurant>
let! withReviews: Restaurant[] =
restaurants
|> Seq.map joinWithReviews
|> Task.WhenAll
I have the following interface method:
Task<string[]> GetBlobsFromContainer(string containerName);
and its implementation in C#:
var container = await _containerClient.GetContainer(containerName);
var tasks = container.ListBlobs()
.Cast<CloudBlockBlob>()
.Select(b => b.DownloadTextAsync());
return await Task.WhenAll(tasks);
When I try to rewrite it in F#:
member this.GetBlobsFromContainer(containerName : string) : Task<string[]> =
let task = async {
let! container = containerClient.GetContainer(containerName) |> Async.AwaitTask
return container.ListBlobs()
|> Seq.cast<CloudBlockBlob>
|> Seq.map (fun b -> b.DownloadTextAsync())
|> ??
}
task |> ??
I'm stuck with the last lines.
How to return to Task<string[]> from F# properly?
I had to guess what the type of containerClient is and the closest I found is CloudBlobClient (which does not have getContainer: string -> Task<CloubBlobContainer> but it shouldn't be too hard to adapt). Then, your function might look like as follows:
open System
open System.Threading.Tasks
open Microsoft.WindowsAzure.Storage.Blob
open Microsoft.WindowsAzure.Storage
let containerClient : CloudBlobClient = null
let GetBlobsFromContainer(containerName : string) : Task<string[]> =
async {
let container = containerClient.GetContainerReference(containerName)
return! container.ListBlobs()
|> Seq.cast<CloudBlockBlob>
|> Seq.map (fun b -> b.DownloadTextAsync() |> Async.AwaitTask)
|> Async.Parallel
} |> Async.StartAsTask
I changed the return type to be Task<string[]> instead of Task<string seq> as I suppose you want to keep the interface. Otherwise, I'd suggest to get rid of the Task and use Async in F#-only code.
Will this work?
member this.GetBlobsFromContainer(containerName : string) : Task<string seq> =
let aMap f x = async {
let! a = x
return f a }
let task = async {
let! container = containerClient.GetContainer(containerName) |> Async.AwaitTask
return! container.ListBlobs()
|> Seq.cast<CloudBlockBlob>
|> Seq.map (fun b -> b.DownloadTextAsync() |> Async.AwaitTask)
|> Async.Parallel
|> aMap Array.toSeq
}
task |> Async.StartAsTask
I had to make some assumptions about containerClient etc. so I haven't been able to test this, but at least it compiles.
Considering the following
type MyClass () =
member x.ReadStreamAsync(stream:Stream) =
async {
let tcs = new TaskCompletionSource<int>()
let buffer = Array.create 2048 0uy
let! bytesReadCount = stream.ReadAsync(buffer, 0, buffer.Length) |> Async.AwaitTask
if bytesReadCount > 0 then
for i in 0..bytesReadCount do
if buffer.[i] = 10uy then
tcs.SetResult(i)
// Omitted more code to handle the case if 10uy is not found..
return tcs.Task
}
The code reads from a stream until in meets a certain character (represented by a byte value) at which point the task returned by the method completes.
The function signature of DoSomethingAsync is unit -> Async<Task<int>>, but I would like it to be unit -> Task<int> such that it can be used more generally in .NET.
Can this be done in F# using an asynchronous expression, or do I can to rely more on the Task constructs of .NET?
Given that you don't actually use the async workflow for anything in your example, the easiest solution would be to forgo it entirely:
member x.DoSomethingAsync() =
let tcs = new TaskCompletionSource<int>()
Task.Delay(100).Wait()
tcs.SetResult(10)
tcs.Task
This implementation of DoSomethingAsync has the type unit -> Task<int>.
It's not clear to me exactly what you're trying to do, but why don't you just do the following?
member x.DoSomethingAsync() =
async {
do! Async.Sleep 100
return 10 } |> Async.StartAsTask
This implementation also has the type unit -> Task<int>.
Based on the updated question, here's a way to do it:
member x.DoSomethingAsync(stream:Stream) =
async {
let buffer = Array.create 2048 0uy
let! bytesReadCount =
stream.ReadAsync(buffer, 0, buffer.Length) |> Async.AwaitTask
if bytesReadCount > 0
then
let res =
[0..bytesReadCount]
|> List.tryFind (fun i -> buffer.[i] = 10uy)
return defaultArg res -1
else return -1
}
|> Async.StartAsTask
The DoSomethingAsync function has the type Stream -> System.Task<int>. I didn't know what to do in the else case, so I just put -1, but I'm sure you can replace it with something more correct.