

GitHub - pimbrouwers/Donald: A simple F# interface for ADO.NET.
source link: https://github.com/pimbrouwers/Donald
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Donald
Meet Donald.
If you're a programmer and have used a database, he's impacted your life in a big way.
This library is named after him.
Honorable mention goes to @dysme another important Donald and F#'s BDFL.
Key Features
Donald is a well-tested library, with pleasant ergonomics that aims to make working with ADO.NET safer and a lot more succinct. It is an entirely generic abstraction, and will work with all ADO.NET implementations.
The library includes multiple computation expressions responsible for building IDbCommand
instances, executed using the Db
module and two result-based expressions for helping with dependent commands (avoiding the dreaded "Pyramid of Doom").
Two sets of type extensions for IDataReader
are included to make manual object mapping a lot easier.
If you came looking for an ORM (object-relational mapper), this is not the library for you. And may the force be with you.
Design Goals
- Support all ADO implementations.
- Provide a natural DSL for interacting with databases.
- Enable asynchronuos workflows.
- Provide explicit error flow control.
- Make object mapping easier.
Getting Started
Install the Donald NuGet package:
PM> Install-Package Donald
Or using the dotnet CLI
dotnet add package Donald
Quick Start
open Donald type Author = { FullName : string } module Author = let ofDataReader (rd : IDataReader) : Author = { FullName = rd.ReadString "full_name" } let authors : DbResult<Author list> = let sql = " SELECT author_id , full_name FROM author WHERE author_id = @author_id" let param = [ "author_id", SqlType.Int 1] use conn = new SQLiteConnection("{your connection string}") conn |> Db.newCommand sql |> Db.setParams param |> Db.query Author.ofDataReader
An Example using SQLite
For this example, assume we have an IDbConnection
named conn
:
Reminder: Donald will work with any ADO implementation (SQL Server, SQLite, MySQL, Postgresql etc.).
Consider the following model:
type Author = { AuthorId : int FullName : string } module Author - let ofDataReader (rd : IDataReader) : Author = { AuthorId = rd.ReadInt32 "author_id" FullName = rd.ReadString "full_name" }
Query for multiple strongly-typed results
// Fluent conn |> Db.newCommand "SELECT author_id, full_name FROM author" |> Db.query Author.ofDataReader // DbResult<Author list> // Expression dbCommand conn { cmdText "SELECT author_id , full_name FROM author" } |> Db.query Author.ofDataReader // DbResult<Author list> // Async conn |> Db.newCommand "SELECT author_id, full_name FROM author" |> Db.Async.query Author.ofDataReader // Task<DbResult<Author list>>
Query for a single strongly-typed result
// Fluent conn |> Db.newCommand "SELECT author_id, full_name FROM author" |> Db.setParams [ "author_id", SqlType.Int 1 ] |> Db.querySingle Author.ofDataReader // DbResult<Author option> // Expression dbCommand conn { cmdText "SELECT author_id , full_name FROM author WHERE author_id = @author_id" cmdParam [ "author_id", SqlType.Int 1] } |> Db.querySingle Author.ofDataReader // DbResult<Author option> // Async conn |> Db.newCommand "SELECT author_id, full_name FROM author" |> Db.setParams [ "author_id", SqlType.Int 1 ] |> Db.Async.querySingle Author.ofDataReader // Task<DbResult<Author option>>
Execute a statement
// Fluent conn |> Db.newCommand "INSERT INTO author (full_name)" |> Db.setParams [ "full_name", SqlType.String "John Doe" ] |> Db.exec // DbResult<unit> // Expression dbCommand conn { cmdText "INSERT INTO author (full_name)" cmdParam [ "full_name", SqlType.String "John Doe" ] } |> Db.exec // DbResult<unit> // Async conn |> Db.newCommand "INSERT INTO author (full_name)" |> Db.setParams [ "full_name", SqlType.String "John Doe" ] |> Db.Async.exec // Task<DbResult<unit>>
Execute a statement many times
// Fluent conn |> Db.newCommand "INSERT INTO author (full_name)" |> Db.execMany [ "full_name", SqlType.String "John Doe" "full_name", SqlType.String "Jane Doe" ] // Expression dbCommand conn { cmdText "INSERT INTO author (full_name)" } |> Db.execMany [ "full_name", SqlType.String "John Doe" "full_name", SqlType.String "Jane Doe" ] // Async conn |> Db.newCommand "INSERT INTO author (full_name)" |> Db.Async.execMany [ "full_name", SqlType.String "John Doe" "full_name", SqlType.String "Jane Doe" ]
Execute statements within an explicit transaction
Donald exposes most of it's functionality through dbCommand { ... }
and the Db
module. But three IDbTransaction
type extension are exposed to make dealing with transactions safer:
TryBeginTransaction()
opens a new transaction or raisesCouldNotBeginTransactionError
TryCommit()
commits a transaction or raisesCouldNotCommitTransactionError
and rolls backTryRollback()
rolls back a transaction or raisesCouldNotRollbackTransactionError
The library also contains a computation expression dbResult { ... }
for dealing with DbResult<'a>
instances, which is especially useful when you are working with dependent commands, common during transactional work.
// Safely begin transaction or throw CouldNotBeginTransactionError on failure use tran = conn.TryBeginTransaction() // Build our IDbCommand's let param = [ "full_name", SqlType.String "John Doe" ] let insertCmd = dbCommand conn { cmdText "INSERT INTO author (full_name)" cmdParam param cmdTran tran } let selectCmd = dbCommand conn { cmdText "SELECT author_id , full_name FROM author WHERE full_name = @full_name" cmdParam param cmdTran tran } // Execute IDbCommand's let result = dbResult { do! insertCmd |> Db.exec return! selectCmd |> Db.querySingle Author.ofDataReader } // Attempt to commit, rollback on failure and throw CouldNotCommitTransactionError tran.TryCommit() // or, safely rollback tran.TryRollback()
This functionality also fully support task-based asynchronous workflows via dbResultTask { ... }
:
// ... rest of code from above let result = dbResultTask { do! insertCmd |> Db.Async.exec return! selectCmd |> Db.Async.querySingle Author.ofDataReader } // ... rest of code from above
Command Builder
At the core of Donald is a computation expression for building IDbCommand
instances. It exposes five modification points:
cmdText
- SQL statement you intend to execute (default:String.empty
).cmdParam
- Input parameters for your statement (default:[]
).cmdType
- Type of command you want to execute (default:CommandType.Text
)cmdTran
- Transaction to assign to command.cmdTimeout
- The maximum time a command can run for (default: underlying DbCommand default, usually 30 seconds)
Reading Values
To make obtaining values from reader more straight-forward, 2 sets of extension methods are available for:
- Get value, automatically defaulted
- Get value as
option<'a>
If you need an explicit
Nullable<'a>
you can useOption.asNullable
.
Assuming we have an active IDataReader
called rd
and are currently reading a row, the following extension methods are available to simplify reading values:
rd.ReadString "some_field" // string -> string rd.ReadBoolean "some_field" // string -> bool rd.ReadByte "some_field" // string -> byte rd.ReadChar "some_field" // string -> char rd.ReadDateTime "some_field" // string -> DateTime rd.ReadDecimal "some_field" // string -> Decimal rd.ReadDouble "some_field" // string -> Double rd.ReadFloat "some_field" // string -> float32 rd.ReadGuid "some_field" // string -> Guid rd.ReadInt16 "some_field" // string -> int16 rd.ReadInt32 "some_field" // string -> int32 rd.ReadInt64 "some_field" // string -> int64 rd.ReadBytes "some_field" // string -> byte[] rd.ReadStringOption "some_field" // string -> string option rd.ReadBooleanOption "some_field" // string -> bool option rd.ReadByteOption "some_field" // string -> byte option rd.ReadCharOption "some_field" // string -> char option rd.ReadDateTimeOption "some_field" // string -> DateTime option rd.ReadDecimalOption "some_field" // string -> Decimal option rd.ReadDoubleOption "some_field" // string -> Double option rd.ReadFloatOption "some_field" // string -> float32 option rd.ReadGuidOption "some_field" // string -> Guid option rd.ReadInt16Option "some_field" // string -> int16 option rd.ReadInt32Option "some_field" // string -> int32 option rd.ReadInt64Option "some_field" // string -> int64 option rd.ReadBytesOption "some_field" // string -> byte[] option
Exceptions
Donald exposes six custom exception types to represent failure at different points in the lifecycle:
exception ConnectionBusyError exception CouldNotOpenConnectionError of exn exception CouldNotBeginTransactionError of exn exception CouldNotCommitTransactionError of exn exception CouldNotRollbackTransactionError of exn
During command execution failures the Error
case of DbResult<'a>
is used, that encapsulates a DbExecutionError
record. These are produced internally as a FailedExecutionError
and transformed by the Db
module.
type DbExecutionError = { Statement : string Error : DbException } type DbResult<'a> = Result<'a, DbExecutionError> exception FailedExecutionError of DbExecutionError
It's important to note that Donald will only raise these exceptions in exceptional situations.
Find a bug?
There's an issue for that.
License
Built with by Pim Brouwers in Toronto, ON. Licensed under Apache License 2.0.
Recommend
-
8
温故而知新,由ADO.NET与Dapper所联想到的2020-12-307 22 min.这段时间在维护一个“遗产项目”,体验可以说是相当地难受,因为它的数据持久化层完全由ADO.NET纯手工打造,所以,你可以在项目中看到无所不在的DataTable,不论是读操作还是写...
-
12
tabs ↹ over ␣ ␣ ␣ spaces by Jiří {x2} Činčura ADO.NET provider 8.0.1.0 for Firebird is ready 7 Apr 2021 1 mins .NET, C#, Databases in general, Entity Fra...
-
5
News & Events Without further ado, YouTube Theater takes the stage
-
3
So far in this series, we've created a simple domain and defined what that would look like in
-
7
In the last post of the series I went over how we could use ADO.NET to go to and from our rich F# domain. As we saw, ADO.NET provides us with the lowest lev...
-
9
Much ado about null ...
-
5
Syncing Cache In ADO.NET Using NCache In .NET 6 With SQL Server...
-
6
Guest Much ado about generative AI: The money, the potential, the pitfalls
-
11
@juxtathinkaJuxtathinkaFreelance Content Writer with a keen interest in creating the best beginner friendly blockchai...
-
12
第二部分:ADO.NET、XML、HTTP、AJAX、WebService1. .NET 和 C# 有什么区别?.NET(通用语言运行时):定义:.NET 是一个软件开发框架,提供了一个通用的运行时环境,用于在不同的编...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK