Files
DynamORM/docs/ado-net-extensions.md

9.5 KiB

ADO.NET Extensions Reference

DynamORM exposes extension helpers mainly in:

  • DynamORM/DynamicExtensions.cs
  • DynamORM/Helpers/DataReaderExtensions.cs
  • DynamORM/Helpers/ReaderExtensions.cs

IDbCommand Extensions

Connection and Transaction

  • SetConnection(this IDbCommand, IDbConnection)
  • SetTransaction(this IDbCommand, IDbTransaction)

These enable fluent command setup.

SetCommand Overloads

Core overloads:

  • SetCommand(CommandType, int timeout, string text, params object[] args)
  • SetCommand(int timeout, string text, params object[] args)
  • SetCommand(CommandType, string text, params object[] args)
  • SetCommand(string text, params object[] args)
  • SetCommand(IDynamicQueryBuilder builder)

Examples:

cmd.SetCommand("SELECT * FROM users WHERE id = {0}", 19);
cmd.SetCommand(CommandType.StoredProcedure, "sp_DoWork");
cmd.SetCommand(builder);

Parameter Helpers

Bulk:

  • AddParameters(DynamicDatabase, params object[] args)
  • AddParameters(DynamicDatabase, ExpandoObject)
  • AddParameters(DynamicDatabase, DynamicExpando)

Single parameter helpers:

  • AddParameter(DynamicDatabase, object item)
  • AddParameter(DynamicDatabase, string name, object item)
  • AddParameter(IDynamicQueryBuilder, DynamicSchemaColumn? col, object value)
  • AddParameter(IDynamicQueryBuilder, DynamicColumn item)

Advanced overloads support explicit ParameterDirection, DbType, size, precision, scale.

Compatibility note on overload resolution:

  • Best practice is to cast value arguments to object when calling AddParameter(...) with value payloads.
  • Alternatively, use named arguments to force the intended overload.
  • This avoids accidental binding to a different overload in ambiguous cases.
  • Current overload shape is preserved for backward compatibility.

Example:

cmd.AddParameter("@Result", ParameterDirection.Output, DbType.String, 256, 0, 0, DBNull.Value);
cmd.AddParameter("@Name", DbType.String, 50, "Alice");

Updating Existing Parameters

  • SetParameter(this IDbCommand, string parameterName, object value)
  • SetParameter(this IDbCommand, int index, object value)

Execution and Conversion

  • ExecuteScalarAs<T>()
  • ExecuteScalarAs<T>(defaultValue)
  • ExecuteScalarAs<T>(TryParseHandler<T>)
  • ExecuteScalarAs<T>(defaultValue, TryParseHandler<T>)
  • ExecuteEnumeratorOf<T>(defaultValue, TryParseHandler<T>)

These convert ADO.NET results to requested types with fallback behavior.

Command Debugging

  • DumpToString()
  • Dump(StringBuilder)
  • Dump(TextWriter)

Useful with DynamicDatabase.DumpCommands and custom log sinks.

IDataReader and Row Helpers

From DynamicExtensions.cs:

  • ToList(this IDataReader)
  • RowToDynamic(this IDataReader)
  • RowToExpando(this IDataReader)
  • RowToDynamic(this DataRow)
  • RowToExpando(this DataRow)
  • upper-case variants (RowToDynamicUpper, RowToExpandoUpper)
  • GetFieldDbType(this IDataReader, int i)

Reader Caching

  • CachedReader(this IDataReader, int offset = 0, int limit = -1, Func<DynamicCachedReader, int, bool> progress = null)

This creates an in-memory DynamicCachedReader snapshot.

DataReaderExtensions

  • ToDataTable(this IDataReader, string name = null, string nameSpace = null)

Converts current reader rows/schema to a DataTable.

ReaderExtensions Null-Safe Accessors

Typed nullable access by column name:

  • GetBooleanIfNotNull
  • GetByteIfNotNull
  • GetCharIfNotNull
  • GetDateTimeIfNotNull
  • GetDecimalIfNotNull
  • GetDoubleIfNotNull
  • GetFloatIfNotNull
  • GetGuidIfNotNull
  • GetInt16IfNotNull
  • GetInt32IfNotNull
  • GetInt64IfNotNull
  • GetStringIfNotNull
  • GetValueIfNotNull

Each method accepts an optional default value and returns it when DB value is null.

DynamicCachedReader

DynamicCachedReader implements IDataReader and stores:

  • schema metadata
  • field names/types/ordinals
  • full row cache (supports multi-result sets)

Construction options:

  • new DynamicCachedReader(IDataReader, offset, limit, progress)
  • DynamicCachedReader.FromDynamicEnumerable(...)
  • DynamicCachedReader.FromEnumerable<T>(...)
  • DynamicCachedReader.FromEnumerable(Type, IEnumerable)

Typical usage:

using (var rdr = cmd.ExecuteReader())
using (var cached = rdr.CachedReader())
{
    while (cached.Read())
    {
        var row = cached.RowToDynamic();
    }
}

When to use it:

  • You need disconnected reader semantics.
  • You need multiple passes or deferred mapping.
  • You need stable materialization before connection disposal.

Tradeoff:

  • Higher memory use proportional to result size.

End-to-End Samples (Genericized)

These samples mirror real production patterns while using generic names.

Sample 1: Stored Procedure with Multiple Result Sets + Null-Safe Reader Accessors

var records = new Dictionary<string, GenericMessage>();

using var con = Database.Open();
using var cmd = con.CreateCommand()
    .SetCommand(CommandType.StoredProcedure, "usp_get_messages");

using var r = cmd.ExecuteReader();

while (r.Read())
{
    var msg = new GenericMessage
    {
        Id = r.GetStringIfNotNull("id"),
        Type = r.GetStringIfNotNull("type"),
        Value = r.GetStringIfNotNull("value"),
        State = (GenericMessageState)(r.GetInt32IfNotNull("state") ?? 0),
        Date = r.GetDateTimeIfNotNull("date") ?? new DateTime(2099, 12, 31),
    };

    records.Add(msg.Id, msg);
}

if (r.NextResult())
{
    while (r.Read())
    {
        var messageId = r.GetStringIfNotNull("message_id");
        if (!records.ContainsKey(messageId))
            continue;

        records[messageId].Items.Add(new GenericMessageItem
        {
            Id = r.GetStringIfNotNull("id"),
            Type = r.GetStringIfNotNull("type"),
            Value = r.GetStringIfNotNull("value"),
        });
    }
}

Extension methods used:

  • SetCommand(CommandType, string)
  • GetStringIfNotNull
  • GetInt32IfNotNull
  • GetDateTimeIfNotNull

Sample 2: Stored Procedure with Input/Output Parameters + Result Check

using var con = Database.Open();
using var cmd = con.CreateCommand()
    .SetCommand(CommandType.StoredProcedure, "usp_set_message_status")
    .AddParameter(Database.GetParameterName("id"), DbType.String, (object)messageId)
    .AddParameter(Database.GetParameterName("status"), DbType.Int32, (object)(int)status)
    .AddParameter(Database.GetParameterName("note"), DbType.String, (object)note)
    .AddParameter(Database.GetParameterName("result"), ParameterDirection.Output, DbType.Int32, 0, null)
    .AddParameter(Database.GetParameterName("result_description"), ParameterDirection.Output, DbType.String, 1024, null);

cmd.ExecuteNonQuery();

var result = cmd.Parameters[Database.GetParameterName("result")] as IDataParameter;
var resultDescription = cmd.Parameters[Database.GetParameterName("result_description")] as IDataParameter;

if (result != null && result.Value != DBNull.Value && result.Value != null)
{
    if ((int)result.Value != 0)
    {
        var description = (resultDescription != null &&
                           resultDescription.Value != DBNull.Value &&
                           resultDescription.Value != null)
            ? resultDescription.Value.ToString()
            : "UNSPECIFIED";

        throw new Exception($"{result.Value} - {description}");
    }
}

Extension methods used:

  • SetCommand(CommandType, string)
  • AddParameter(...) overloads with DbType and ParameterDirection
  • DumpToString() (optional diagnostic logging)

Sample 3: Parameterized UPDATE with Runtime SQL and ADO.NET Extensions

using var conn = Database.Open();

using var cmdUpdate = conn.CreateCommand()
    .SetCommand($@"UPDATE {Database.DecorateName("transport_task")}
SET {Database.DecorateName("confirmed")} = {Database.GetParameterName("confirmed")}
WHERE {Database.DecorateName("id")} = {Database.GetParameterName("id")}")
    .AddParameter(Database.GetParameterName("confirmed"), DbType.Int32, (object)1)
    .AddParameter(Database.GetParameterName("id"), DbType.Guid, (object)taskId);

if (cmdUpdate.ExecuteNonQuery() > 0)
{
    // updated
}

Extension methods used:

  • SetCommand(string, params object[])
  • AddParameter(string, DbType, object)

Sample 4: Parameterized SELECT + Reader Mapping Method

using var conn = Database.Open();

using var cmd = conn.CreateCommand()
    .SetCommand($@"SELECT * FROM {Database.DecorateName("transport_task")}
WHERE {Database.DecorateName("id")} = {Database.GetParameterName("id")}")
    .AddParameter(Database.GetParameterName("id"), DbType.Guid, (object)id);

using var r = cmd.ExecuteReader();
if (r.Read())
    return ReadTask(r);

return null;

protected virtual MoveTask ReadTask(IDataReader r)
{
    var source = r.GetStringIfNotNull("Source");
    var destination = r.GetStringIfNotNull("Destination");

    return new MoveTask
    {
        Id = r.GetGuid(r.GetOrdinal("Id")),
        Created = r.GetDateTime(r.GetOrdinal("Created")),
        StartDate = r.GetDateTimeIfNotNull("StartDate"),
        EndDate = r.GetDateTimeIfNotNull("EndDate"),
        ExternalId = r.GetStringIfNotNull("ExternalId"),
        TaskType = (TransportTaskType)(r.GetInt32IfNotNull("Type") ?? 0),
        Source = string.IsNullOrEmpty(source) ? null : Deserialize<Address>(source),
        Destination = string.IsNullOrEmpty(destination) ? null : Deserialize<Address>(destination),
        Priority = r.GetInt32IfNotNull("Priority") ?? 0,
    };
}

Extension methods used:

  • SetCommand(...)
  • AddParameter(...)
  • GetStringIfNotNull
  • GetDateTimeIfNotNull
  • GetInt32IfNotNull