diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml
index b66238d..b82f24f 100644
--- a/.github/workflows/publish-release.yml
+++ b/.github/workflows/publish-release.yml
@@ -46,4 +46,5 @@ jobs:
dotnet nuget push "FruityFoundation.FsBase.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
dotnet nuget push "FruityFoundation.Db.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
dotnet nuget push "FruityFoundation.DataAccess.Abstractions.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
+ dotnet nuget push "FruityFoundation.DataAccess.Abstractions.FSharp.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
dotnet nuget push "FruityFoundation.DataAccess.Core.${{ steps.generate-version.outputs.RELEASE_VERSION_NUMBER }}.nupkg" --api-key=${{ secrets.NUGET_API_KEY }} --source=https://api.nuget.org/v3/index.json --skip-duplicate
diff --git a/FruityFoundation.DataAccess.Abstractions.FSharp/FruityFoundation.DataAccess.Abstractions.FSharp.fsproj b/FruityFoundation.DataAccess.Abstractions.FSharp/FruityFoundation.DataAccess.Abstractions.FSharp.fsproj
new file mode 100644
index 0000000..a2f32f6
--- /dev/null
+++ b/FruityFoundation.DataAccess.Abstractions.FSharp/FruityFoundation.DataAccess.Abstractions.FSharp.fsproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+ true
+ true
+ 1.11.0
+ Kyle Ratti
+ https://github.com/kyleratti/FruityFoundation
+ true
+ LICENSE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FruityFoundation.DataAccess.Abstractions.FSharp/ReadOnlyDb.fs b/FruityFoundation.DataAccess.Abstractions.FSharp/ReadOnlyDb.fs
new file mode 100644
index 0000000..34f3560
--- /dev/null
+++ b/FruityFoundation.DataAccess.Abstractions.FSharp/ReadOnlyDb.fs
@@ -0,0 +1,35 @@
+[]
+module FruityFoundation.DataAccess.Abstractions.FSharp.ReadOnlyDb
+
+open System.Collections.Generic
+open System.Threading
+open FSharp.Control
+open FruityFoundation.DataAccess.Abstractions
+
+let private toKeyValuePair (parms : (string * obj) seq) =
+ parms
+ |> Seq.map (fun (name, value) -> KeyValuePair(name, value))
+
+let query<'a> (connection : IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
+ return! connection.Query<'a>(sql, parms |> toKeyValuePair, cancellationToken)
+}
+
+let queryUnbuffered<'a> (connection : IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = taskSeq {
+ yield! connection.QueryUnbuffered<'a>(sql, parms |> toKeyValuePair, cancellationToken)
+}
+
+let querySingle<'a> (connection : IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
+ return! connection.QuerySingle<'a>(sql, parms |> toKeyValuePair, cancellationToken)
+}
+
+let execute (connection : IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
+ return! connection.Execute(sql, parms |> toKeyValuePair, cancellationToken)
+}
+
+let executeScalar<'a> (connection : IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
+ return! connection.ExecuteScalar<'a>(sql, parms |> toKeyValuePair, cancellationToken)
+}
+
+let executeReader (connection : IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
+ return! connection.ExecuteReader(sql, parms |> toKeyValuePair, cancellationToken)
+}
diff --git a/FruityFoundation.DataAccess.Abstractions.FSharp/ReadWriteDb.fs b/FruityFoundation.DataAccess.Abstractions.FSharp/ReadWriteDb.fs
new file mode 100644
index 0000000..b6309e4
--- /dev/null
+++ b/FruityFoundation.DataAccess.Abstractions.FSharp/ReadWriteDb.fs
@@ -0,0 +1,35 @@
+[]
+module FruityFoundation.DataAccess.Abstractions.FSharp.ReadWriteDb
+
+open System.Collections.Generic
+open System.Threading
+open FSharp.Control
+open FruityFoundation.DataAccess.Abstractions
+
+let private toKeyValuePair (parms : (string * obj) seq) =
+ parms
+ |> Seq.map (fun (name, value) -> KeyValuePair(name, value))
+
+let query<'a> (connection : IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
+ return! connection.Query<'a>(sql, parms |> toKeyValuePair, cancellationToken)
+}
+
+let queryUnbuffered<'a> (connection : IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = taskSeq {
+ yield! connection.QueryUnbuffered<'a>(sql, parms |> toKeyValuePair, cancellationToken)
+}
+
+let querySingle<'a> (connection : IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
+ return! connection.QuerySingle<'a>(sql, parms |> toKeyValuePair, cancellationToken)
+}
+
+let execute (connection : IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
+ return! connection.Execute(sql, parms |> toKeyValuePair, cancellationToken)
+}
+
+let executeScalar<'a> (connection : IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
+ return! connection.ExecuteScalar<'a>(sql, parms |> toKeyValuePair, cancellationToken)
+}
+
+let executeReader (connection : IDatabaseConnection) (cancellationToken : CancellationToken) (sql : string) (parms : (string * obj) seq) = task {
+ return! connection.ExecuteReader(sql, parms |> toKeyValuePair, cancellationToken)
+}
diff --git a/FruityFoundation.Tests.DataAccess.Abstractions.FSharp/FruityFoundation.Tests.DataAccess.Abstractions.FSharp.fsproj b/FruityFoundation.Tests.DataAccess.Abstractions.FSharp/FruityFoundation.Tests.DataAccess.Abstractions.FSharp.fsproj
new file mode 100644
index 0000000..a9ce4ad
--- /dev/null
+++ b/FruityFoundation.Tests.DataAccess.Abstractions.FSharp/FruityFoundation.Tests.DataAccess.Abstractions.FSharp.fsproj
@@ -0,0 +1,30 @@
+
+
+
+ net8.0
+
+ false
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FruityFoundation.Tests.DataAccess.Abstractions.FSharp/Program.fs b/FruityFoundation.Tests.DataAccess.Abstractions.FSharp/Program.fs
new file mode 100644
index 0000000..176a7b6
--- /dev/null
+++ b/FruityFoundation.Tests.DataAccess.Abstractions.FSharp/Program.fs
@@ -0,0 +1,4 @@
+module Program =
+
+ []
+ let main _ = 0
diff --git a/FruityFoundation.Tests.DataAccess.Abstractions.FSharp/ReadOnlyDbTests.fs b/FruityFoundation.Tests.DataAccess.Abstractions.FSharp/ReadOnlyDbTests.fs
new file mode 100644
index 0000000..c92a6cf
--- /dev/null
+++ b/FruityFoundation.Tests.DataAccess.Abstractions.FSharp/ReadOnlyDbTests.fs
@@ -0,0 +1,249 @@
+module FruityFoundation.Tests.DataAccess.Abstractions.FSharp.ReadOnlyDbTests
+
+open System.Collections.Generic
+open System.Threading
+open FSharp.Control
+open FakeItEasy
+open FruityFoundation.DataAccess.Abstractions
+open FruityFoundation.DataAccess.Abstractions.FSharp
+open NUnit.Framework
+
+let fakeDbConnection : IDatabaseConnection = A.Fake> ()
+
+[]
+let Db_Query_Calls_IDatabaseConnection_Query_NoParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = Array.empty
+
+ // Act
+ let! _ = task {
+ return! (sql, parms) ||> ReadOnlyDb.query fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.Query(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(Seq.isEmpty),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_Query_Calls_IDatabaseConnection_Query_WithParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = [| ("@id", box 1) |]
+
+ // Act
+ let! result = task {
+ return! (sql, parms) ||> ReadOnlyDb.query fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.Query(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(fun x ->
+ Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_QueryUnbuffered_Calls_IDatabaseConnection_Query_NoParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = Array.empty
+
+ // Act
+ let! _ = task {
+ return! (sql, parms)
+ ||> ReadOnlyDb.queryUnbuffered fakeDbConnection CancellationToken.None
+ |> TaskSeq.toArrayAsync
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.QueryUnbuffered(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(Seq.isEmpty),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_QueryUnbuffered_Calls_IDatabaseConnection_Query_WithParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = [| ("@id", box 1) |]
+
+ // Act
+ let! result = task {
+ return! (sql, parms)
+ ||> ReadOnlyDb.queryUnbuffered fakeDbConnection CancellationToken.None
+ |> TaskSeq.toArrayAsync
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.QueryUnbuffered(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(fun x ->
+ Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_QuerySingle_Calls_IDatabaseConnection_QuerySingle_NoParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = Array.empty
+
+ // Act
+ let! _ = task {
+ return! (sql, parms) ||> ReadOnlyDb.querySingle fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.QuerySingle(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(Seq.isEmpty),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_QuerySingle_Calls_IDatabaseConnection_QuerySingle_WithParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = [| ("@id", box 1) |]
+
+ // Act
+ let! result = task {
+ return! (sql, parms) ||> ReadOnlyDb.querySingle fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.QuerySingle(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(fun x ->
+ Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_Execute_Calls_IDatabaseConnection_Execute_NoParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = Array.empty
+
+ // Act
+ let! _ = task {
+ return! (sql, parms) ||> ReadOnlyDb.execute fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.Execute(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(Seq.isEmpty),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_Execute_Calls_IDatabaseConnection_Execute_WithParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = [| ("@id", box 1) |]
+
+ // Act
+ let! result = task {
+ return! (sql, parms) ||> ReadOnlyDb.execute fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.Execute(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(fun x ->
+ Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_ExecuteScalar_Calls_IDatabaseConnection_ExecuteScalar_NoParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = Array.empty
+
+ // Act
+ let! _ = task {
+ return! (sql, parms) ||> ReadOnlyDb.executeScalar fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.ExecuteScalar(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(Seq.isEmpty),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_ExecuteScalar_Calls_IDatabaseConnection_ExecuteScalar_WithParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = [| ("@id", box 1) |]
+
+ // Act
+ let! result = task {
+ return! (sql, parms) ||> ReadOnlyDb.executeScalar fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.ExecuteScalar(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(fun x ->
+ Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_ExecuteReader_Calls_IDatabaseConnection_ExecuteReader_NoParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = Array.empty
+
+ // Act
+ let! _ = task {
+ return! (sql, parms) ||> ReadOnlyDb.executeReader fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.ExecuteReader(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(Seq.isEmpty),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_ExecuteReader_Calls_IDatabaseConnection_ExecuteReader_WithParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = [| ("@id", box 1) |]
+
+ // Act
+ let! result = task {
+ return! (sql, parms) ||> ReadOnlyDb.executeReader fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.ExecuteReader(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(fun x ->
+ Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
diff --git a/FruityFoundation.Tests.DataAccess.Abstractions.FSharp/ReadWriteDbTests.fs b/FruityFoundation.Tests.DataAccess.Abstractions.FSharp/ReadWriteDbTests.fs
new file mode 100644
index 0000000..4faefe6
--- /dev/null
+++ b/FruityFoundation.Tests.DataAccess.Abstractions.FSharp/ReadWriteDbTests.fs
@@ -0,0 +1,249 @@
+module FruityFoundation.Tests.DataAccess.Abstractions.FSharp.ReadWriteDbTests
+
+open System.Collections.Generic
+open System.Threading
+open FSharp.Control
+open FakeItEasy
+open FruityFoundation.DataAccess.Abstractions
+open FruityFoundation.DataAccess.Abstractions.FSharp
+open NUnit.Framework
+
+let fakeDbConnection : IDatabaseConnection = A.Fake> ()
+
+[]
+let Db_Query_Calls_IDatabaseConnection_Query_NoParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = Array.empty
+
+ // Act
+ let! _ = task {
+ return! (sql, parms) ||> ReadWriteDb.query fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.Query(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(Seq.isEmpty),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_Query_Calls_IDatabaseConnection_Query_WithParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = [| ("@id", box 1) |]
+
+ // Act
+ let! result = task {
+ return! (sql, parms) ||> ReadWriteDb.query fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.Query(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(fun x ->
+ Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_QueryUnbuffered_Calls_IDatabaseConnection_Query_NoParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = Array.empty
+
+ // Act
+ let! _ = task {
+ return! (sql, parms)
+ ||> ReadWriteDb.queryUnbuffered fakeDbConnection CancellationToken.None
+ |> TaskSeq.toArrayAsync
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.QueryUnbuffered(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(Seq.isEmpty),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_QueryUnbuffered_Calls_IDatabaseConnection_Query_WithParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = [| ("@id", box 1) |]
+
+ // Act
+ let! result = task {
+ return! (sql, parms)
+ ||> ReadWriteDb.queryUnbuffered fakeDbConnection CancellationToken.None
+ |> TaskSeq.toArrayAsync
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.QueryUnbuffered(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(fun x ->
+ Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_QuerySingle_Calls_IDatabaseConnection_QuerySingle_NoParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = Array.empty
+
+ // Act
+ let! _ = task {
+ return! (sql, parms) ||> ReadWriteDb.querySingle fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.QuerySingle(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(Seq.isEmpty),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_QuerySingle_Calls_IDatabaseConnection_QuerySingle_WithParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = [| ("@id", box 1) |]
+
+ // Act
+ let! result = task {
+ return! (sql, parms) ||> ReadWriteDb.querySingle fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.QuerySingle(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(fun x ->
+ Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_Execute_Calls_IDatabaseConnection_Execute_NoParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = Array.empty
+
+ // Act
+ let! _ = task {
+ return! (sql, parms) ||> ReadWriteDb.execute fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.Execute(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(Seq.isEmpty),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_Execute_Calls_IDatabaseConnection_Execute_WithParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = [| ("@id", box 1) |]
+
+ // Act
+ let! result = task {
+ return! (sql, parms) ||> ReadWriteDb.execute fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.Execute(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(fun x ->
+ Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_ExecuteScalar_Calls_IDatabaseConnection_ExecuteScalar_NoParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = Array.empty
+
+ // Act
+ let! _ = task {
+ return! (sql, parms) ||> ReadWriteDb.executeScalar fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.ExecuteScalar(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(Seq.isEmpty),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_ExecuteScalar_Calls_IDatabaseConnection_ExecuteScalar_WithParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = [| ("@id", box 1) |]
+
+ // Act
+ let! result = task {
+ return! (sql, parms) ||> ReadWriteDb.executeScalar fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.ExecuteScalar(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(fun x ->
+ Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_ExecuteReader_Calls_IDatabaseConnection_ExecuteReader_NoParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = Array.empty
+
+ // Act
+ let! _ = task {
+ return! (sql, parms) ||> ReadWriteDb.executeReader fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.ExecuteReader(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(Seq.isEmpty),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
+
+[]
+let Db_ExecuteReader_Calls_IDatabaseConnection_ExecuteReader_WithParms () = task {
+ // Arrange
+ let sql = "SELECT 1 FROM table"
+ let parms = [| ("@id", box 1) |]
+
+ // Act
+ let! result = task {
+ return! (sql, parms) ||> ReadWriteDb.executeReader fakeDbConnection CancellationToken.None
+ }
+
+ // Assert
+ A.CallTo(fun () -> fakeDbConnection.ExecuteReader(
+ "SELECT 1 FROM table",
+ A>>.That.Matches(fun x ->
+ Seq.length x = 1 && Seq.head x = KeyValuePair("@id", box 1)),
+ CancellationToken.None)).MustHaveHappenedOnceExactly ()
+ |> ignore
+}
diff --git a/FruityFoundation.sln b/FruityFoundation.sln
index 7b46a1c..3130355 100644
--- a/FruityFoundation.sln
+++ b/FruityFoundation.sln
@@ -20,6 +20,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.DataAccess
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.DataAccess.Core", "FruityFoundation.DataAccess.Core\FruityFoundation.DataAccess.Core.csproj", "{B65527CC-218A-4EA3-93DC-985713B5DFF4}"
EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FruityFoundation.DataAccess.Abstractions.FSharp", "FruityFoundation.DataAccess.Abstractions.FSharp\FruityFoundation.DataAccess.Abstractions.FSharp.fsproj", "{B2E0156B-B631-4C80-A129-59472D2D0A77}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FruityFoundation.Tests.DataAccess.Abstractions.FSharp", "FruityFoundation.Tests.DataAccess.Abstractions.FSharp\FruityFoundation.Tests.DataAccess.Abstractions.FSharp.fsproj", "{27F4FB64-7A51-4315-BDAA-6EE07736C976}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -58,6 +62,14 @@ Global
{B65527CC-218A-4EA3-93DC-985713B5DFF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B65527CC-218A-4EA3-93DC-985713B5DFF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B65527CC-218A-4EA3-93DC-985713B5DFF4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B2E0156B-B631-4C80-A129-59472D2D0A77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B2E0156B-B631-4C80-A129-59472D2D0A77}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B2E0156B-B631-4C80-A129-59472D2D0A77}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B2E0156B-B631-4C80-A129-59472D2D0A77}.Release|Any CPU.Build.0 = Release|Any CPU
+ {27F4FB64-7A51-4315-BDAA-6EE07736C976}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {27F4FB64-7A51-4315-BDAA-6EE07736C976}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {27F4FB64-7A51-4315-BDAA-6EE07736C976}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {27F4FB64-7A51-4315-BDAA-6EE07736C976}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{50A75644-A1C3-4495-9DEB-DBB12D9334B5} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
@@ -65,5 +77,7 @@ Global
{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
{C003E247-C62E-4830-94E4-F274D8466A5C} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
{B65527CC-218A-4EA3-93DC-985713B5DFF4} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
+ {B2E0156B-B631-4C80-A129-59472D2D0A77} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
+ {27F4FB64-7A51-4315-BDAA-6EE07736C976} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
EndGlobalSection
EndGlobal