Querying

Every method here is an extension method on DbConnection, every one has an …Async counterpart, and all of them accept an optional transaction, command timeout, command type and cancellation token.

The examples assume the static helpers are imported:

using static RentADeveloper.DbConnectionPlus.DbConnectionExtensions;

General-purpose methods

ExecuteNonQuery / ExecuteNonQueryAsync

Executes an SQL statement and returns the number of rows affected by the statement.

if (supplier.IsRetired)
{
    var numberOfDeletedProducts = connection.ExecuteNonQuery(
       $"""
        DELETE FROM Product
        WHERE       SupplierId = {Parameter(supplier.Id)}
        """
    );
}

ExecuteReader / ExecuteReaderAsync

Executes an SQL statement and returns a DbDataReader to read the results.

var lowStockThreshold = configuration.Thresholds.LowStock;

using var lowStockProductsReader = connection.ExecuteReader(
   $"""
    SELECT  *
    FROM    Product
    WHERE   UnitsInStock < {Parameter(lowStockThreshold)}
    """
);

ExecuteScalar / ExecuteScalarAsync

Executes an SQL statement and returns the value of the first column of the first row in the result set converted to the specified type.

var lowStockThreshold = configuration.Thresholds.LowStock;

var numberOfLowStockProducts = connection.ExecuteScalar<int>(
   $"""
    SELECT  COUNT(*)
    FROM    Product
    WHERE   UnitsInStock < {Parameter(lowStockThreshold)}
    """
);

Exists / ExistsAsync

Checks if any rows exist that match the specified SQL statement.

var lowStockThreshold = configuration.Thresholds.LowStock;

var existLowStockProducts = connection.Exists(
   $"""
    SELECT  1
    FROM    Product
    WHERE   UnitsInStock < {Parameter(lowStockThreshold)}
    """
);

Query methods

The non-generic query methods below return DataRow instances. There are two ways to read a column, and which one you should use depends on how your application is published:

Access Works on
Recommended product["Id"] — string indexer, no cast every runtime, including Native AOT
Optional product.Id — member access through a dynamic reference runtimes with dynamic code generation (not Native AOT)

The examples in this section use the string indexer. To use member access instead, assign the row to a dynamic reference — the static return type is DataRow, not dynamic, so the step is explicit:

dynamic product = connection.QueryFirst($"SELECT * FROM Product WHERE Id = {Parameter(id)}");
var name = product.Name;

foreach (dynamic p in connection.Query($"SELECT * FROM Product"))
{
    var unitsInStock = p.UnitsInStock;
}

Member access behaves exactly like the indexer, including throwing KeyNotFoundException for a column the row does not contain. Note that through a dynamic reference a property always addresses a column — row.Count reads the column named Count, not the number of columns — while method calls still resolve against DataRow, so row.ContainsKey("Id") works as expected. Use a statically typed DataRow reference to reach Count, Keys and Values.

If you publish with Native AOT, use the indexer: the C# compiler reports dynamic usage as an AOT incompatibility at your own call site, and the Dynamic Language Runtime cannot bind it without run-time code generation. DataRow itself is AOT-safe to construct and use either way. See Native AOT and trimming.

Query / QueryAsync

Executes an SQL statement and maps the result set to a sequence of DataRow instances. Access columns by name through the string indexer.

var lowStockThreshold = configuration.Thresholds.LowStock;

var lowStockProducts = connection.Query(
   $"""
    SELECT  *
    FROM    Product
    WHERE   UnitsInStock < {Parameter(lowStockThreshold)}
    """
);

foreach (var product in lowStockProducts)
{
    var id = product["Id"];
    var unitsInStock = product["UnitsInStock"];
    ...
}

QueryFirst / QueryFirstAsync

Executes an SQL statement and maps the first row of the result set to a DataRow. Throws if no rows are found.

var product = connection.QueryFirst($"SELECT * FROM Product WHERE Id = {Parameter(id)}");

var id = product["Id"];
var name = product["Name"];
...

QueryFirstOrDefault / QueryFirstOrDefaultAsync

Executes an SQL statement and maps the first row of the result set to a DataRow or null if no rows are found.

var product = connection.QueryFirstOrDefault($"SELECT * FROM Product WHERE Id = {Parameter(id)}");

if (product is not null)
{
    var id = product["Id"];
    var name = product["Name"];
    ...
}

QuerySingle / QuerySingleAsync

Executes an SQL statement and maps the single row of the result set to a DataRow. Throws if no rows or more than one row are found.

var product = connection.QuerySingle($"SELECT * FROM Product WHERE Id = {Parameter(id)}");

var id = product["Id"];
var name = product["Name"];
...

QuerySingleOrDefault / QuerySingleOrDefaultAsync

Executes an SQL statement and maps the single row of the result set to a DataRow or null if no rows are found. Throws if more than one row are found.

var product = connection.QuerySingleOrDefault($"SELECT * FROM Product WHERE Id = {Parameter(id)}");

if (product is not null)
{
    var id = product["Id"];
    var name = product["Name"];
    ...
}

Query<T> / QueryAsync<T>

Executes an SQL statement and maps the result set to a sequence of scalar values, entities or value tuples of the specified type.

var lowStockThreshold = configuration.Thresholds.LowStock;

// Entities
var lowStockProducts = connection.Query<Product>(
   $"""
    SELECT  *
    FROM    Product
    WHERE   UnitsInStock < {Parameter(lowStockThreshold)}
    """
);

// Scalar values
var lowStockProductIds = connection.Query<long>(
   $"""
    SELECT  Id
    FROM    Product
    WHERE   UnitsInStock < {Parameter(lowStockThreshold)}
    """
);

// Value tuples
var lowStockProductInfos = connection.Query<(long ProductId, int UnitsInStock)>(
   $"""
    SELECT  Id, UnitsInStock
    FROM    Product
    WHERE   UnitsInStock < {Parameter(lowStockThreshold)}
    """
);

QueryFirst<T> / QueryFirstAsync<T>

Executes an SQL statement and maps the first row of the result set to a scalar value, entity or value tuple of the specified type. Throws if no rows are found.

var product = connection.QueryFirst<Product>($"SELECT * FROM Product WHERE Id = {Parameter(id)}");

QueryFirstOrDefault<T> / QueryFirstOrDefaultAsync<T>

Executes an SQL statement and maps the first row of the result set to a scalar value, entity or value tuple of the specified type or default value if no rows are found.

var product = connection.QueryFirstOrDefault<Product>($"SELECT * FROM Product WHERE Id = {Parameter(id)}");

QuerySingle<T> / QuerySingleAsync<T>

Executes an SQL statement and maps the single row of the result set to a scalar value, entity or value tuple of the specified type. Throws if no rows or more than one row are found.

var product = connection.QuerySingle<Product>($"SELECT * FROM Product WHERE Id = {Parameter(id)}");

QuerySingleOrDefault<T> / QuerySingleOrDefaultAsync<T>

Executes an SQL statement and maps the single row of the result set to a scalar value, entity or value tuple of the specified type or default value if no rows are found. Throws if more than one row are found.

var product = connection.QuerySingleOrDefault<Product>($"SELECT * FROM Product WHERE Id = {Parameter(id)}");