Table of Contents

FAQ

How is a QueryCommand accessed

static readonly QueryCommand GetAlbum = new("SELECT AlbumId AS Id, Title FROM albums WHERE AlbumId = @albumId");

Album album = GetAlbum.Query<Album>(cnn, new { albumId = 12 });
// The application holds this QueryCommand directly.
Album album = cnn.Query<Album>("SELECT AlbumId AS Id, Title FROM albums WHERE AlbumId = @albumId", new { albumId = 12 });
// The exact SQL string accesses the cached QueryCommand.

SQL string cache

Can one command be shared across threads

static readonly QueryCommand GetAlbum = new("SELECT AlbumId AS Id, Title FROM albums WHERE AlbumId = @albumId");

Album[] albums = await Task.WhenAll(
    GetAlbum.QueryAsync<Album>((DbConnection)firstConnection, new { albumId = 12 }),
    GetAlbum.QueryAsync<Album>((DbConnection)secondConnection, new { albumId = 46 }));

Per-call values stay outside the QueryCommand. Builder state is also held separately from the command.

Builders

Can one command run across different providers

A QueryCommand can retain mapping and parameter metadata learned from a provider. A command shared across different providers is unsupported.

static readonly QueryCommand SqlServerAlbums = new("SELECT AlbumId AS Id, Title FROM albums");
static readonly QueryCommand PostgreSqlAlbums = new("SELECT AlbumId AS Id, Title FROM albums");

Parameter metadata · Cache control

Does Rinku rewrite named parameters for positional providers

var command = new QueryCommand("SELECT UserId, Name FROM users WHERE UserId = ? AND Status = ?", ["userId", "status"], CommandType.Text);

The placeholders remain provider syntax. The declared variables give Rinku the parameter order.

Positional parameters

Why did the provider report a missing parameter

A plain variable remains required while its SQL remains.

SELECT AlbumId AS Id, Title FROM albums WHERE AlbumId = @albumId

A conditional variable removes its complete condition when its value is absent.

SELECT AlbumId AS Id, Title FROM albums WHERE AlbumId = ?@albumId

Without albumId the SQL becomes.

SELECT AlbumId AS Id, Title FROM albums

Conditional variables

How can optional filters avoid WHERE 1=1

static readonly QueryCommand SearchAlbums = new("SELECT AlbumId AS Id, Title FROM albums WHERE ArtistId = ?@artistId AND ReleaseYear >= ?@minimumYear");

List<Album> albums = SearchAlbums.Query<List<Album>>(cnn, new { artistId = 7 });
// SELECT AlbumId AS Id, Title FROM albums WHERE ArtistId = @artistId

Conditional variables

Why did Query report no values

An unwrapped result requests the first complete mapped value.

static readonly QueryCommand FindAlbum = new("SELECT AlbumId AS Id, Title FROM albums WHERE AlbumId = @albumId");

Album album = FindAlbum.Query<Album>(cnn, new { albumId = 999 });
// No complete Album produces RINKU4001.

A wrapper can represent no returned value.

Optional<Album> album = FindAlbum.Query<Optional<Album>>(cnn, new { albumId = 999 });

Result shapes

Why did IN with an empty collection disappear

static readonly QueryCommand FindAlbums = new("SELECT AlbumId AS Id, Title FROM albums WHERE AlbumId IN (?@albumIds_X)");

List<Album> albums = FindAlbums.Query<List<Album>>(cnn, new { albumIds = Array.Empty<int>() });
// SELECT AlbumId AS Id, Title FROM albums

An empty collection is absent for the collection handler. A required @albumIds_X raises RINKU2002 instead.

Collection expansion · RINKU2002

Why is a nested type unavailable

public record Artist(int Id, string Name) : IDbReadable;
public record Album(int Id, string Title, Artist Artist);

Album is the explicitly requested root. Artist becomes available for nested mapping through IDbReadable.

The same registration can be made externally.

public record Artist(int Id, string Name);

TypeParsingInfo.GetOrAdd<Artist>();

Registration

Why did joined rows produce several parent objects

Rows that fold into one parent group must be consecutive.

SELECT ar.ArtistId AS Id, ar.Name, al.AlbumId AS AlbumsId, al.Title AS AlbumsTitle FROM artists ar JOIN albums al ON al.ArtistId = ar.ArtistId ORDER BY ar.ArtistId

The grouping page shows inferred boundaries, explicit keys, and custom grouping rules.

Grouping

When are streamed output parameters available

Output values are available after the stream finishes or its enumerator is disposed.

QueryCommand readAndCountAlbums = QueryCommand.FromProc("ReadAndCountAlbums", cnn);
IEnumerable<Album> albums = readAndCountAlbums.Query<IEnumerable<Album>>(cnn, out DbCommand command);

using (command)
{
    using (IEnumerator<Album> iterator = albums.GetEnumerator())
    {
        if (iterator.MoveNext())
            Console.WriteLine(iterator.Current.Title);
    }

    int moved = command.GetOutputValue<int>("@moved");
}

Stored procedure output values

Where is the Dapper comparison

Coming from Dapper

Does Tracking save changes to the database

Album original = new(12, "Blue", new Artist(7, "Miles"));
IRuntimeTrackingItem<Album> edit = RuntimeTracking.Default<Album>().Create(original);
edit.Set(nameof(Album.Title), "Kind of Blue");

foreach (TrackingChange change in edit.GetChanges())
    Console.WriteLine($"{change.Name} {change.OriginalValue} -> {change.Value}");
static readonly QueryCommand UpdateAlbum = new("UPDATE albums SET Title = @Title WHERE AlbumId = @Id");

using DbTransaction transaction = cnn.BeginTransaction();

if (edit.HasChanges())
    UpdateAlbum.Execute(cnn, edit, transaction: transaction);

transaction.Commit();
edit.ConfirmEdit();

Tracking persistence

Which connection is used with a transaction

Pass the same open connection that created the transaction.

using DbTransaction transaction = cnn.BeginTransaction();

UpdateAlbum.Execute(cnn, new { albumId = 12, title = "Updated" }, transaction: transaction);

Rinku passes the supplied connection and transaction to the provider.

Execution context

How are several result sets read

static readonly QueryCommand GetDashboard = new("SELECT ArtistId AS Id, Name FROM artists WHERE ArtistId = @artistId; SELECT AlbumId AS Id, Title FROM albums WHERE ArtistId = @artistId");

using MultiReader results = GetDashboard.ExecuteMultiReader(cnn, new { artistId = 7 });

Artist artist = results.Query<Artist>();
List<Album> albums = results.Query<List<Album>>();

Multiple result sets

Which databases can CodeGen inspect

SQL Server    SQL queries, SQL files, stored procedures
PostgreSQL    SQL queries, SQL files, stored procedures
SQLite        SQL queries, SQL files

CodeGen configuration · Query sources

Do generated commands require a Rinku result parser

using DbCommand command = cnn.GetAlbumsByArtist(artistId: 7);
using DbDataReader reader = command.ExecuteReader();
// Generated methods return `DbCommand` instances.
static readonly CachedTypeParser<List<GetAlbumsByArtistResult>> AlbumsParser = new();

List<GetAlbumsByArtistResult> albums = AlbumsParser.Query(cnn.GetAlbumsByArtist(artistId: 7));

Generated commands

Why does generated CodeGen output contain an error directive

#error Query generation failed for method 'GetBrokenAlbums'

The query named by the directive failed during discovery or generation. Other valid query entries can still be generated.

Refresh generated code