Table of Contents

Expressive commands

An expressive command is a small fluent object: pick the SQL (or stored procedure), add parameters, then call a terminal that runs it.

Two starting points

The factory form (connectionFactory.Command(sql) or connectionFactory.StoredProcedure(name)) and the connection form (connection.Command(sql) or connection.StoredProcedure(name)) look the same after the first call. Side by side, row against row:

Factory form

// nothing: the factory creates, opens and disposes the connection
List<Post> posts = connectionFactory.Command(ListSql)
    .AddParam("author", 42)
    .Map<Post>().ToList();

Connection form

using var connection = new SqliteConnection(connectionString);
List<Post> posts = connection.Command(ListSql)
    .AddParam("author", 42)
    .Map<Post>().ToList();

In the factory form the command creates, opens and disposes a connection for every terminal. In the connection form the connection is yours: the command opens it only when it is closed, closes only what it opened, and never disposes it. See who owns the connection.

A command is not safe to share across threads: one built from a single connection throws InvalidOperationException if two terminals run at once.

Which command type you get

You call it on You get Reader type
IDbConnection, IDbTransaction, IDbConnectionFactory ExpressiveCommand IDataReader
DbConnection, DbTransaction, IDbConnectionFactory<TConnection> ExpressiveDbCommand DbDataReader (async reads)
SqlConnection or a SqlConnectionFactory (MSSqlClient package) ExpressiveSqlCommand SqlDataReader

Pass a CommandType as the second argument of Command if you need TableDirect.

Parameters

AddParam(name, value) adds an input parameter. Overloads accept a database type and a ParameterDirection. AddParamIf(condition, name, value) adds the parameter only when the condition is true, which keeps the SQL text constant:

// AddParamIf adds the parameter only when the condition is true: use it when the SQL text varies with the same condition.
string sql = author.HasValue
    ? "select * from posts where author = @author order by id"
    : "select * from posts order by id";

Post[] posts = connectionFactory
    .Command(sql)
    .AddParamIf(author.HasValue, "author", author)
    .Map<Post>()
    .ToArray();

AddReturnParam registers a return value; call ExecuteReturn<T>() to read it.

Terminals

Terminal Returns
ExecuteNonQuery() Rows affected
ExecuteScalar(), ExecuteScalar<T>() The first column of the first row
Execute(command => ...) Whatever your function returns, given the raw IDbCommand
ExecuteReader(reader => ...) Whatever your function returns, given the live reader
Map<T>() then a terminal Mapped objects: see Mapping rows to objects
ToChannel<T>(), ToTargetBlock<T>() See Channels and Dataflow

ExpressiveDbCommand and ExpressiveSqlCommand add Async twins (for example ExecuteNonQueryAsync()) that honor the command's cancellation token (UseCancellationToken(token)). SetTimeout(seconds) sets the command timeout.

ExecuteScalar<T>() casts the value the provider returns, so T must be that exact type. SQLite returns a long for count(*) and SQL Server returns an int. To convert, pass a function instead:

// ExecuteScalar<T> casts the value the provider returns, so T must be that exact type
// (SQLite counts as long; SQL Server counts as int). Use the Func overload to convert instead.
long count = connectionFactory
    .Command("select count(*) from posts where author = @author")
    .AddParam("author", 42)
    .ExecuteScalar<long>();

int updated = connectionFactory
    .Command("update posts set views = views + 1 where author = @author")
    .AddParam("author", 42)
    .ExecuteNonQuery();
int count = connectionFactory
    .Command("select count(*) from posts")
    .ExecuteScalar(value => Convert.ToInt32(value));