Loading…

BitBadger.Npgsql.Documents
Advanced Usage: Transactions

On occasion, there may be a need to perform multiple updates in a single database transaction, where either all updates succeed, or none do.

Controlling Database Transactions

The NpgsqlDataSource object has an OpenConnectionAsync() method which returns an open database connection; this connection can be used to start a transaction. (There is also a synchronous version.)

// C#
    using var conn = await Configuration.DataSource().OpenConnectionAsync();
    using var txn = await conn.BeginTransactionAsync();
    try
    {
        // do stuff
        await txn.CommitAsync();
    }
    catch (Exception ex)
    {
        await txn.RollbackAsync();
        // more error handling
    }
// F#
    use! conn = Configuration.DataSource().OpenConnectionAsync ()
    use! txn = conn.BeginTransactionAsync ()
    try
        // do stuff
        do! txn.CommitAsync ()
    with ex ->
        do! txt.RollbackAsync ()
        // more error handling

Providing This Connection to Queries

The above gets us a connection against which the transaction is being executed; but how do we tell the library about that connection? The answer is a static class or module we have yet to discuss - WithProps. Npgsql.FSharp uses SqlProps to build up a query which is then executed; this was covered a bit when we discussed related documents. WithProps contains all of the function definitions covered up to this point in the documentation, with SqlProps as the final parameter for the call.

First, we need SqlProps that know about our connection:

// C#
    var props = Sql.existingConnection(conn);
// F#
    let props = Sql.existingConnection conn

Then, we can pass these props to the queries. Imagine an application where a user signs in. We may want to set an attribute on the user record that says that now is the last time they signed in; and we may also want to reset a failed logon counter, as they have successfully signed in. This would look like:

// C#
    using var conn = await Configuration.DataSource().OpenConnectionAsync();
    using var txn = await conn.BeginTransactionAsync();
    var props = Sql.existingConnection(conn);
    try
    {
        WithProps.Update.PartialById("user_table", userId, new { LastSeen = DateTime.Now }, props);
        WithProps.Update.PartialById("security", userId, new { FailedLogOnCount = 0 }, props);
        await txn.CommitAsync();
    }
    catch (Exception ex)
    {
        await txn.RollbackAsync();
        // more error handling
    }
// F#
    use! conn = Configuration.dataSource().OpenConnectionAsync ()
    use! txn = conn.BeginTransactionAsync ()
    let props = Sql.existingConnection conn
    try
        WithProps.Update.partialById "user_table" userId {| LastSeen = DateTime.Now |} props
        WithProps.Update.partialById "security" userId {| FailedLogOnCount = 0 |} props
        do! txn.CommitAsync ()
    with ex ->
        do! txn.RollbackAsync ()
        // more error handling

WithProps can be used as the default, with either using static BitBadger.Npgsql.Documents.Document.WithProps; in C# or open BitBadger.Npgsql.FSharp.Documents.WithProps in F#. In most cases, though, using it as the examples above do will be preferred; this allows the library to use its own connection handling for retrieval queries.

« Back to Advanced Usage