Description
Description
Some stored procedures return result sets of data, but others just return int status codes to indicate success or failure. SQLProvider appears to ignore those status codes, and calling Invoke
or InvokeAsync
on one of those procedures returns Unit
or Async<Unit>
respectively.
When one of these procedures throws an error, Invoke
raises a SqlException
with the text of the error thrown from the procedure. This can be caught using a normal try...with
block. However, the InvokeAsync
method does not appear to catch this error, and the code continues as if the procedure was successful.
Also mentioned in the last comment in #535.
Repro steps
Use this SQL to create a table, procedure, and some example rows:
-- Create example table
DROP TABLE IF EXISTS [dbo].[TempTesting]
GO
CREATE TABLE [dbo].[TempTesting](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](50) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
GO
-- Create example stored procedure
CREATE OR ALTER PROCEDURE [dbo].[spDoTempTesting]
@Name VARCHAR (50)
AS
BEGIN
DECLARE
@existingId INT
,@errorMsg VARCHAR(50)
;
SELECT @existingId = [Id] FROM [dbo].[TempTesting] WHERE Name = @Name;
IF @existingId IS NULL
BEGIN
SET @errorMsg = CONCAT(
'Could not find name [',
@Name,
']'
);
THROW 50001, @errorMsg, 1
END
--SELECT * FROM [dbo].[TempTesting] WHERE Id = @existingId;
RETURN 0
END
GO
-- Create example data
INSERT INTO [dbo].[TempTesting] ([Name]) VALUES ('John'), ('Sarah'), ('George')
GO
Now, run this F# code:
// #r statements excluded for brevity
// Also, assume the variable connectionString here represents a valid connection string.
open FSharp.Data.Sql
type sql = SqlDataProvider<
ConnectionString = connectionString,
DatabaseVendor = Common.DatabaseProviderTypes.MSSQLSERVER
>
let ctx = sql.GetDataContext(SelectOperations.DatabaseSide)
let f name =
try
let normalResult = ctx.Procedures.SpDoTempTesting.Invoke(``@Name``=name)
printfn "Normal result succeeded:\n%A\n\n" normalResult
with e ->
eprintfn "Normal result failed:\n%A\n\n" e
let asyncResult =
ctx.Procedures.SpDoTempTesting.InvokeAsync(``@Name``=name)
|> Async.Catch
|> Async.RunSynchronously
match asyncResult with
| Choice1Of2 c -> printfn "Async result succeeded:\n%A\n\n" c
| Choice2Of2 c -> eprintfn "Async result failed:\n%A\n\n" c
// Should not error, since we created a John record
f "John"
// Should error, since we did not create this one
f "NotARealName"
These are the results of running the above code in F# interactive:
Normal result succeeded:
<null>
Async result succeeded:
<null>
Async result succeeded:
<null>
Normal result failed:
System.Data.SqlClient.SqlException (0x80131904): Could not find name [NotARealName]
...stack trace...
Finally, for comparison, modify and run the SQL by uncommenting the last SELECT line and commenting the RETURN line at the end. Repeat the F# code and compare the results.
Normal result succeeded:
FSharp.Data.Sql.Common.SqlEntity
Async result succeeded:
FSharp.Data.Sql.Common.SqlEntity
Normal result failed:
System.Data.SqlClient.SqlException (0x80131904): Could not find name [NotARealName]
...stack trace...
Async result failed:
System.AggregateException: One or more errors occurred. ---> System.Data.SqlClient.SqlException: Could not find name [NotARealName]
at System.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__180_0(Task`1 result)
at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke
at System.Threading.Tasks.Task.Execute()
--- End of inner exception stack trace ---
---> (Inner Exception #0) System.Data.SqlClient.SqlException (0x80131904): Could not find name [NotARealName]
at System.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__180_0(Task`1 result)
at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke
at System.Threading.Tasks.Task.Execute()
ClientConnectionId:ab1983fc-944c-4afa-af60-d28a758a9af8
Error Number:50001,State:1,Class:16<---
Expected behavior
InvokeAsync
should return Choice2Of2
with an exception in both test cases.
Actual behavior
In the first case, InvokeAsync
appears to succeed, while Invoke
returns a clear failure message. In the second test case, both Invoke
and InvokeAsync
failed as expected.
Known workarounds
I can think of two workarounds:
- Modify all stored procedures to return data, not just result codes
- Modify all F# code calling stored procedures to be synchronous
Neither of these are great options.
Related information
- Used database: Microsoft SQL Server 14.0.2027
- Operating system: Windows 10
- Branch: NuGet release, version 1.1.76
- .NET Runtime, CoreCLR or Mono Version: .NET Core 3.1
- Performance information, links to performance testing scripts: N/A