Donald 9.0.0
See the version list below for details.
dotnet add package Donald --version 9.0.0
NuGet\Install-Package Donald -Version 9.0.0
<PackageReference Include="Donald" Version="9.0.0" />
paket add Donald --version 9.0.0
#r "nuget: Donald, 9.0.0"
// Install Donald as a Cake Addin #addin nuget:?package=Donald&version=9.0.0 // Install Donald as a Cake Tool #tool nuget:?package=Donald&version=9.0.0
Donald
Meet Donald (Chamberlin).
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 @dsyme another important Donald and F#'s BDFL.
Key Features
Donald is a well-tested library 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.
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 succinct, type-safe API for interacting with databases
- Enable asynchronuos workflows
- Provide explicit error flow control
- Make object mapping easier
- Improve data access performance
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 : Result<Author list, DbError> =
let sql = "
SELECT 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
Important: Donald is set to use
CommandBehavior.SequentialAccess
by default. See performance for more information.
let sql = "SELECT author_id, full_name FROM author"
conn
|> Db.newCommand sql
|> Db.query Author.ofDataReader // Result<Author list, DbError>
// Async
conn
|> Db.newCommand sql
|> Db.Async.query Author.ofDataReader // Task<Result<Author list, DbError>>
Query for a single strongly-typed result
let sql = "SELECT author_id, full_name FROM author"
conn
|> Db.newCommand sql
|> Db.setParams [ "author_id", SqlType.Int 1 ]
|> Db.querySingle Author.ofDataReader // Result<Author option, DbError>
// Async
conn
|> Db.newCommand sql
|> Db.setParams [ "author_id", SqlType.Int 1 ]
|> Db.Async.querySingle Author.ofDataReader // Task<Result<Author option, DbError>>
Execute a statement
let sql = "INSERT INTO author (full_name)"
// Strongly typed input parameters
let param = [ "full_name", SqlType.String "John Doe" ]
conn
|> Db.newCommand sql
|> Db.setParams param
|> Db.exec // Result<unit, DbError>
// Async
conn
|> Db.newCommand sql
|> Db.setParams param
|> Db.Async.exec // Task<Result<unit, DbError>>
Execute a statement many times
let sql = "INSERT INTO author (full_name)"
let param =
[ "full_name", SqlType.String "John Doe"
"full_name", SqlType.String "Jane Doe" ]
conn
|> Db.newCommand sql
|> Db.execMany param
// Async
conn
|> Db.newCommand sql
|> Db.Async.execMany param
let sql = "INSERT INTO author (full_name)"
let param = [ "full_name", SqlType.String "John Doe" ]
conn
|> Db.newCommand sql
|> Db.setParams param
|> Db.exec // Result<unit, DbError>
// Async
conn
|> Db.newCommand sql
|> Db.setParams param
|> Db.Async.exec // Task<Result<unit, DbError>>
Execute statements within an explicit transaction
Donald exposes most of it's functionality through the Db
module. But three IDbTransaction
type extension are exposed to make dealing with transactions safer:
TryBeginTransaction()
opens a new transaction or raisesDbTransactionError
TryCommit()
commits a transaction or raisesDbTransactionError
and rolls backTryRollback()
rolls back a transaction or raisesDbTransactionError
// Safely begin transaction or throw CouldNotBeginTransactionError on failure
use tran = conn.TryBeginTransaction()
let insertSql = "INSERT INTO author (full_name)"
let param = [ "full_name", SqlType.String "John Doe" ]
let insertResult =
conn
|> Db.newCommand insertSql
|> Db.setTransaction tran
|> Db.setParams param
|> Db.exec
match insertResult with
| Ok () ->
// Attempt to commit, rollback on failure and throw CouldNotCommitTransactionError
tran.TryCommit ()
conn
|> Db.newCommand "SELECT author_id, full_name FROM author WHERE full_name = @full_name"
|> Db.setParams param
|> Db.querySingle Author.ofDataReader
| Error e ->
// Attempt to commit, rollback on failure and throw CouldNotCommitTransactionError
tran.TryRollback ()
Error e
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 DbError
type to represent failure at different points in the execution-cycle, all of which are encapsulated within a general DbFailureException
.
type DbError =
| DbConnectionError of DbConnectionError
| DbTransactionError of DbTransactionError
| DbExecutionError of DbExecutionError
| DataReaderCastError of DataReaderCastError
| DataReaderOutOfRangeError of DataReaderOutOfRangeError
exception DbFailureException of DbError
During command execution failures the Error
case of Result
contains one of DbError
union cases with relevant data.
/// Details of failure to connection to a database/server.
type DbConnectionError =
{ ConnectionString : string
Error : exn }
/// Details the steps of database a transaction.
type DbTransactionStep = TxBegin | TxCommit | TxRollback
/// Details of transaction failure.
type DbTransactionError =
{ Step : DbTransactionStep
Error : exn }
/// Details of failure to execute database command.
type DbExecutionError =
{ Statement : string
Error : DbException }
/// Details of failure to cast a IDataRecord field.
type DataReaderCastError =
{ FieldName : string
Error : InvalidCastException }
/// Details of failure to access a IDataRecord column by name.
type DataReaderOutOfRangeError =
{ FieldName : string
Error : IndexOutOfRangeException }
It's important to note that Donald will only raise exceptions in exceptional situations.
Performance
By default, Donald will consume IDataReader
using CommandBehavior.SequentialAccess
. This allows the rows and columns to be read in chunks (i.e., streamed), but forward-only. As opposed to being completely read into memory all at once, and readable in any direction. The benefits of this are particular felt when reading large CLOB (string) and BLOB (binary) data. But is also a measureable performance gain for standard query results as well.
The only nuance to sequential access is that columns must be read in the same order found in the SELECT
clause. Aside from that, there is no noticeable difference from the perspective of a library consumer.
Configuring CommandBehavior
can be done two ways:
let sql = "SELECT author_id, full_name FROM author"
conn
|> Db.newCommand sql
|> Db.setCommandBehavior CommandBehavior.Default
|> Db.query Author.ofDataReader
Find a bug?
There's an issue for that.
License
Built with ♥ by Pim Brouwers in Toronto, ON. Licensed under Apache License 2.0.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net6.0
- FSharp.Core (>= 7.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
10.1.0 | 2,266 | 3/20/2024 |
10.0.2 | 1,100 | 12/12/2023 |
10.0.1 | 967 | 7/11/2023 |
10.0.0 | 152 | 7/8/2023 |
10.0.0-alpha3 | 183 | 2/11/2023 |
10.0.0-alpha2 | 159 | 2/4/2023 |
10.0.0-alpha1 | 145 | 2/3/2023 |
9.0.1 | 1,334 | 1/11/2023 |
9.0.0 | 315 | 12/23/2022 |
8.0.2 | 15,305 | 11/23/2022 |
8.0.1 | 323 | 11/23/2022 |
8.0.0 | 392 | 11/23/2022 |
7.1.0 | 2,706 | 12/17/2021 |
7.0.0 | 332 | 12/14/2021 |
7.0.0-alpha1 | 273 | 11/1/2021 |
6.2.5 | 631 | 8/4/2021 |
6.2.4 | 383 | 8/4/2021 |
6.2.3 | 390 | 7/30/2021 |
6.2.2 | 466 | 7/27/2021 |
6.2.1 | 358 | 7/27/2021 |
6.2.0 | 575 | 7/26/2021 |
6.1.0 | 473 | 7/6/2021 |
6.1.0-beta3 | 234 | 7/5/2021 |
6.1.0-beta2 | 244 | 7/4/2021 |
6.1.0-beta1 | 348 | 7/4/2021 |
6.0.0 | 425 | 4/11/2021 |
5.1.3 | 422 | 3/29/2021 |
5.1.2 | 447 | 2/27/2021 |
5.1.1 | 458 | 1/23/2021 |
5.0.1 | 1,046 | 12/3/2020 |
5.0.0 | 432 | 12/1/2020 |
5.0.0-alpha3 | 291 | 12/1/2020 |
5.0.0-alpha2 | 268 | 11/30/2020 |
5.0.0-alpha1 | 272 | 11/30/2020 |
4.0.0 | 488 | 11/12/2020 |
3.0.4 | 1,762 | 10/31/2020 |
3.0.3 | 743 | 8/2/2020 |
3.0.2 | 530 | 7/17/2020 |
3.0.1 | 514 | 7/14/2020 |
3.0.0 | 554 | 6/29/2020 |
2.0.2 | 485 | 5/1/2020 |
2.0.1 | 454 | 4/27/2020 |
2.0.0 | 463 | 4/27/2020 |
1.0.6 | 438 | 4/24/2020 |
1.0.4 | 422 | 4/24/2020 |
1.0.3 | 452 | 4/24/2020 |
1.0.2 | 454 | 4/24/2020 |
1.0.1 | 658 | 4/18/2020 |
1.0.0 | 490 | 4/5/2020 |