{"id":15392965,"url":"https://github.com/pimbrouwers/donald","last_synced_at":"2025-05-15T13:07:34.324Z","repository":{"id":41251670,"uuid":"251025716","full_name":"pimbrouwers/Donald","owner":"pimbrouwers","description":"A lightweight, generic F# database abstraction.","archived":false,"fork":false,"pushed_at":"2024-12-02T12:39:29.000Z","size":259,"stargazers_count":187,"open_issues_count":2,"forks_count":20,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-05-13T21:49:52.253Z","etag":null,"topics":["ado","ado-net","dotnet","fsharp","sql"],"latest_commit_sha":null,"homepage":"","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pimbrouwers.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-03-29T12:23:20.000Z","updated_at":"2025-04-21T07:23:58.000Z","dependencies_parsed_at":"2024-04-02T02:30:11.804Z","dependency_job_id":"a19edaf3-1f2a-4945-9e6b-610571daac55","html_url":"https://github.com/pimbrouwers/Donald","commit_stats":{"total_commits":194,"total_committers":14,"mean_commits":"13.857142857142858","dds":0.5618556701030928,"last_synced_commit":"a237f25a63cc580a6dce13d9d627dd3c5da45c3e"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pimbrouwers%2FDonald","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pimbrouwers%2FDonald/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pimbrouwers%2FDonald/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pimbrouwers%2FDonald/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pimbrouwers","download_url":"https://codeload.github.com/pimbrouwers/Donald/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254346624,"owners_count":22055808,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ado","ado-net","dotnet","fsharp","sql"],"created_at":"2024-10-01T15:16:52.292Z","updated_at":"2025-05-15T13:07:34.271Z","avatar_url":"https://github.com/pimbrouwers.png","language":"F#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Donald\n\n[![NuGet Version](https://img.shields.io/nuget/v/Donald.svg)](https://www.nuget.org/packages/Donald)\n[![build](https://github.com/pimbrouwers/Donald/actions/workflows/build.yml/badge.svg)](https://github.com/pimbrouwers/Donald/actions/workflows/build.yml)\n\nMeet [Donald](https://en.wikipedia.org/wiki/Donald_D._Chamberlin) (Chamberlin).\n\nIf you're a programmer and have used a database, he's impacted your life in a big way.\n\nThis library is named after him.\n\n\u003e Honorable mention goes to [@dsyme](https://github.com/dsyme) another important Donald and F#'s [BDFL](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life).\n\n## Key Features\n\nDonald is a generic library that aims to make working with [ADO.NET](https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/ado-net-overview) safer and more succinct. It is an entirely generic abstraction, and will work with all ADO.NET implementations.\n\n## Design Goals\n\n- Support all ADO implementations\n- Provide a succinct, type-safe API for interacting with databases\n- Enable asynchronuos workflows\n- Make object mapping easier\n- Improve data access performance\n- Provide additional context during exceptions\n\n## Getting Started\n\nInstall the [Donald](https://www.nuget.org/packages/Donald/) NuGet package:\n\n```\nPM\u003e  Install-Package Donald\n```\n\nOr using the dotnet CLI\n```cmd\ndotnet add package Donald\n```\n\n### Quick Start\n\n```fsharp\nopen Donald\n\ntype Author =\n    { AuthorId : int\n      FullName : string }\n\nlet authors (conn : IDbConnection) : Author list =\n    conn\n    |\u003e Db.newCommand \"\n        SELECT  author_id\n              , full_name\n        FROM    author\n        WHERE   book_count \u003e= @book_count\"\n    |\u003e Db.setParams [\n        \"book_count\", SqlType.Int32 3 ]\n    |\u003e Db.query (fun rd -\u003e\n        { AuthorId = rd.ReadInt \"author_id\"\n          FullName = rd.ReadString \"full_name\" })\n```\n\n## An Example using SQLite\n\nFor this example, assume we have an `IDbConnection` named `conn`:\n\n\u003e Reminder: Donald will work with __any__ ADO implementation (SQL Server, SQLite, MySQL, Postgresql etc.).\n\nConsider the following model:\n\n```fsharp\ntype Author =\n    { AuthorId : int\n      FullName : string }\n\nmodule Author -\n    let ofDataReader (rd : IDataReader) : Author =\n        { AuthorId = rd.ReadInt32 \"author_id\"\n          FullName = rd.ReadString \"full_name\" }\n```\n\n### Query for multiple strongly-typed results\n\n\u003e Important: Donald is set to use `CommandBehavior.SequentialAccess` by default. See [performance](#performance) for more information.\n\n```fsharp\nconn\n|\u003e Db.newCommand \"SELECT author_id, full_name FROM author\"\n|\u003e Db.query Author.ofDataReader // Author list\n\n// Async\nconn\n|\u003e Db.newCommand \"SELECT author_id, full_name FROM author\"\n|\u003e Db.Async.query Author.ofDataReader // Task\u003cAuthor list\u003e\n```\n\n### Query for a single strongly-typed result\n\n```fsharp\nconn\n|\u003e Db.newCommand \"SELECT author_id, full_name FROM author\"\n|\u003e Db.setParams [ \"author_id\", SqlType.Int32 1 ]\n|\u003e Db.querySingle Author.ofDataReader // Author option\n\n// Async\nconn\n|\u003e Db.newCommand \"SELECT author_id, full_name FROM author\"\n|\u003e Db.setParams [ \"author_id\", SqlType.Int32 1 ]\n|\u003e Db.Async.querySingle Author.ofDataReader // Task\u003cAuthor option\u003e\n```\n\n### Execute a statement\n\n```fsharp\nconn\n|\u003e Db.newCommand \"INSERT INTO author (full_name) VALUES (@full_name)\"\n|\u003e Db.setParams [ \"full_name\", SqlType.String \"John Doe\" ]\n|\u003e Db.exec // unit\n\n// Async\nconn\n|\u003e Db.newCommand \"INSERT INTO author (full_name) VALUES (@full_name)\"\n|\u003e Db.setParams [ \"full_name\", SqlType.String \"John Doe\" ]\n|\u003e Db.Async.exec // Task\u003cunit\u003e\n```\n\n### Execute a statement many times\n\n```fsharp\nconn\n|\u003e Db.newCommand \"INSERT INTO author (full_name) VALUES (@full_name)\"\n|\u003e Db.execMany [\n    \"full_name\", SqlType.String \"John Doe\"\n    \"full_name\", SqlType.String \"Jane Doe\" ] // unit\n\n// Async\nconn\n|\u003e Db.newCommand \"INSERT INTO author (full_name) VALUES (@full_name)\"\n|\u003e Db.Async.execMany [\n    \"full_name\", SqlType.String \"John Doe\"\n    \"full_name\", SqlType.String \"Jane Doe\" ] //Task\u003cunit\u003e\n```\n\n### Execute statements within an explicit transaction\n\nThis can be accomplished in two ways:\n\n1. Using `Db.batch` or `Db.Async.batch` which processes the action in an *all-or-none* fashion.\n\n```fsharp\nconn\n|\u003e Db.batch (fun tran -\u003e\n    for fullName in [ \"John Doe\"; \"Jane Doe\" ] do\n        tran\n        |\u003e Db.newCommandForTransaction \"INSERT INTO author (full_name) VALUES (@full_name)\"\n        |\u003e Db.setParams [\"full_name\", SqlType.String fullName ]\n        |\u003e Db.exec)\n```\n\n2. Using the extension methods: `TryBeginTransaction()`, `TryCommit()` and `TryRollback()`.\n\n```fsharp\n// Safely begin transaction or throw CouldNotBeginTransactionError on failure\nuse tran = conn.TryBeginTransaction()\n\nfor fullName in [ \"John Doe\"; \"Jane Doe\" ] do\n    tran\n    |\u003e Db.newCommandForTransaction \"INSERT INTO author (full_name) VALUES (@full_name)\"\n    |\u003e Db.setParams [\"full_name\", SqlType.String fullName ]\n    |\u003e Db.exec\n\n// Attempt to commit, will rollback automatically on failure, or throw DbTransactionException\ntran.TryCommit ()\n\n// Will rollback or throw DbTransactionException\n// tran.TryRollback ()\n```\n\n## Command Parameters\n\nCommand parameters are represented by `SqlType` which contains a case for all relevant types.\n\n```fsharp\ntype SqlType =\n    | Null\n    | String     of string\n    | AnsiString of string\n    | Boolean    of bool\n    | Byte       of byte\n    | Char       of char\n    | AnsiChar   of char\n    | Decimal    of decimal\n    | Double     of double\n    | Float      of float\n    | Guid       of Guid\n    | Int16      of int16\n    | Int32      of int32\n    | Int        of int32\n    | Int64      of int64\n    | DateTime   of DateTime\n    | Bytes      of byte[]\n\nlet p1 : SqlType = SqlType.Null\nlet p2 : SqlType = SqlType.Int32 1\n```\n\nHelpers also exist which implicitly call the respective F# conversion function. Which can be especially useful when you are working with value types in your program.\n\n```fsharp\nlet p1 : SqlType = sqlInt32 \"1\" // equivalent to SqlType.Int32 (int \"1\")\n```\n\n###\n\n## Reading Values\n\nTo make obtaining values from reader more straight-forward, 2 sets of extension methods are available for:\n1. Get value, automatically defaulted\n2. Get value as `option\u003c'a\u003e`\n\nAssuming we have an active `IDataReader` called `rd` and are currently reading a row, the following extension methods are available to simplify reading values:\n\n```fsharp\nrd.ReadString \"some_field\"         // string -\u003e string\nrd.ReadBoolean \"some_field\"        // string -\u003e bool\nrd.ReadByte \"some_field\"           // string -\u003e byte\nrd.ReadChar \"some_field\"           // string -\u003e char\nrd.ReadDateTime \"some_field\"       // string -\u003e DateTime\nrd.ReadDecimal \"some_field\"        // string -\u003e Decimal\nrd.ReadDouble \"some_field\"         // string -\u003e Double\nrd.ReadFloat \"some_field\"          // string -\u003e float32\nrd.ReadGuid \"some_field\"           // string -\u003e Guid\nrd.ReadInt16 \"some_field\"          // string -\u003e int16\nrd.ReadInt32 \"some_field\"          // string -\u003e int32\nrd.ReadInt64 \"some_field\"          // string -\u003e int64\nrd.ReadBytes \"some_field\"          // string -\u003e byte[]\n\nrd.ReadStringOption \"some_field\"   // string -\u003e string option\nrd.ReadBooleanOption \"some_field\"  // string -\u003e bool option\nrd.ReadByteOption \"some_field\"     // string -\u003e byte option\nrd.ReadCharOption \"some_field\"     // string -\u003e char option\nrd.ReadDateTimeOption \"some_field\" // string -\u003e DateTime option\nrd.ReadDecimalOption \"some_field\"  // string -\u003e Decimal option\nrd.ReadDoubleOption \"some_field\"   // string -\u003e Double option\nrd.ReadFloatOption \"some_field\"    // string -\u003e float32 option\nrd.ReadGuidOption \"some_field\"     // string -\u003e Guid option\nrd.ReadInt16Option \"some_field\"    // string -\u003e int16 option\nrd.ReadInt32Option \"some_field\"    // string -\u003e int32 option\nrd.ReadInt64Option \"some_field\"    // string -\u003e int64 option\nrd.ReadBytesOption \"some_field\"    // string -\u003e byte[] option\n```\n\n\u003e If you need an explicit `Nullable\u003c'a\u003e` you can use `Option.asNullable`.\n\n## Exceptions\n\nSeveral custom exceptions exist which interleave the exceptions thrown by ADO.NET with contextually relevant metadata.\n\n```fsharp\n/// Details of failure to connection to a database/server.\ntype DbConnectionException =\n    inherit Exception\n    val ConnectionString : string option\n\n/// Details of failure to execute database command or transaction.\ntype DbExecutionException =\n    inherit Exception\n    val Statement : string option\n    val Step : DbTransactionStep option\n\n/// Details of failure to access and/or cast an IDataRecord field.\ntype DbReaderException =\n    inherit Exception\n    val FieldName : string option\n\n/// Details of failure to commit or rollback an IDbTransaction\ntype DbTransactionException =\n    inherit Exception\n    val Step : DbTransactionStep\n```\n\n## Performance\n\nBy default, the `IDataReader` is consumed 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.\n\nThe 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.\n\nConfiguring `CommandBehavior` can be done two ways:\n\n```fsharp\nlet sql = \"SELECT author_id, full_name FROM author\"\n\nconn\n|\u003e Db.newCommand sql\n|\u003e Db.setCommandBehavior CommandBehavior.Default\n|\u003e Db.query Author.ofDataReader\n```\n\n## Find a bug?\n\nThere's an [issue](https://github.com/pimbrouwers/Donald/issues) for that.\n\n## License\n\nBuilt with ♥ by [Pim Brouwers](https://github.com/pimbrouwers) in Toronto, ON. Licensed under [Apache License 2.0](https://github.com/pimbrouwers/Donald/blob/master/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpimbrouwers%2Fdonald","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpimbrouwers%2Fdonald","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpimbrouwers%2Fdonald/lists"}