cmd.GetByReader - lobodava/artisan-orm GitHub Wiki
cmd.GetByReader executes a command and passes its open SqlDataReader to a projection callback. It:
- opens the connection if it is closed;
- executes the command and obtains a reader;
- invokes the synchronous callback and retains its result;
- disposes the reader;
- closes the connection only if this call opened it, then returns the result.
The command remains owned by the caller (or by the enclosing GetByCommand wrapper). A connection already opened by the caller remains open.
| Method | Signature after the this SqlCommand cmd argument |
|---|---|
GetByReader<T> |
T GetByReader<T>(Func<SqlDataReader, T> func) |
GetByReaderAsync<T> |
Task<T> GetByReaderAsync<T>(Func<SqlDataReader, T> func, CancellationToken cancellationToken = default) |
Both callbacks are synchronous. The async method awaits command execution, then invokes the projection while the reader is open. An async callback can infer a task-valued T, but the helper would dispose the reader before that task finishes. Do not use it for asynchronous reader traversal, and do not return a lazy sequence that still needs the reader after the callback returns.
This helper is useful for several SELECT result sets. Materializing reader extensions such as ReadTo<T>(), ReadToList<T>(), and ReadToArray<T>() advance to the next result set by default, even when the current set is empty. Do not add another NextResult() between those calls. Use getNextResult: false only when deliberately retaining the current set.
The examples below are methods of a repository derived from RepositoryBase. They assume a registered User mapper for Id, Login, Name, Email, and a byte[] RoleIds property. The second result set contains RoleId tinyint.
public User? GetUserById(int id)
{
return GetByCommand(cmd =>
{
cmd.UseProcedure("dbo.GetUserById");
cmd.AddIntParam("@Id", id);
return cmd.GetByReader(reader =>
{
var user = reader.ReadTo<User>();
var roleIds = reader.ReadToArray<byte>();
if (user != null)
user.RoleIds = roleIds;
return user;
});
});
}The async form below makes command execution asynchronous; its projection still reads synchronously:
public Task<User?> GetUserByIdAsync(int id, CancellationToken cancellationToken)
{
return GetByCommandAsync((cmd, token) =>
{
cmd.UseProcedure("dbo.GetUserById");
cmd.AddIntParam("@Id", id);
return cmd.GetByReaderAsync(reader =>
{
var user = reader.ReadTo<User>();
var roleIds = reader.ReadToArray<byte>();
if (user != null)
user.RoleIds = roleIds;
return user;
}, token);
}, cancellationToken);
}The 4.1.0 helper forwards the token to OpenAsync and command execution. Its callback remains synchronous and controls its own traversal and cancellation checks. For asynchronous callback work, explicitly own the reader throughout that work and use ReadAsync / NextResultAsync with the token. Reader-level ReadToAsyncEnumerable / ReadAsAsyncEnumerable can traverse the current result set; they neither dispose the supplied reader nor advance to the next result set.
CREATE PROCEDURE dbo.GetUserById
@Id int
AS
BEGIN
SET NOCOUNT ON;
SELECT Id, [Login], Name, Email FROM dbo.Users WHERE Id = @Id;
SELECT RoleId FROM dbo.UserRoles WHERE UserId = @Id;
END;Match the mapper's column order and types to the real schema. If the procedure additionally emits a DataReply header, consume it explicitly before these result sets; see Artisan Way of Data Reply.
Sources: synchronous command helpers, async command helpers, reader extensions.