Files
DynamORM/docs/stored-procedures.md

6.5 KiB

Stored Procedures

Stored procedure support is available through db.Procedures when DynamicDatabaseOptions.SupportStoredProcedures is enabled.

This page documents actual runtime behavior from DynamicProcedureInvoker.

Invocation Basics

var scalar = db.Procedures.sp_Exp_Scalar();
var scalarTyped = db.Procedures.sp_Exp_Scalar<int>();

Schema-qualified naming is built through dynamic chaining:

var res = db.Procedures.dbo.reporting.MyProc();

Final command name is dbo.reporting.MyProc.

Parameter Direction Prefixes

Because dynamic out/ref is limited, parameter direction is encoded by argument name prefix:

  • out_ => ParameterDirection.Output
  • ret_ => ParameterDirection.ReturnValue
  • both_ => ParameterDirection.InputOutput

Example:

dynamic res = db.Procedures.sp_Message_SetState(
    id: "abc-001",
    both_state: "pending",
    out_message: "",
    ret_code: 0);

Prefix is removed from the exposed output key (out_message -> message).

How to Specify Type/Length for Out Parameters

This is the most common pain point. You have 2 primary options.

Use this when you need explicit type, length, precision, or scale.

dynamic res = db.Procedures.sp_Message_SetState(
    id: "abc-001",
    out_message: new DynamicSchemaColumn
    {
        Name = "message",
        Type = DbType.String,
        Size = 1024,
    },
    ret_code: 0);

Use this when you need direction + value + schema in one object.

dynamic res = db.Procedures.sp_Message_SetState(
    both_state: new DynamicColumn
    {
        ColumnName = "state",
        ParameterDirection = ParameterDirection.InputOutput,
        Value = "pending",
        Schema = new DynamicSchemaColumn
        {
            Name = "state",
            Type = DbType.String,
            Size = 32,
        }
    });

Option C: Plain value + name prefix

Quickest form, but type/size inference is automatic.

dynamic res = db.Procedures.sp_Message_SetState(
    id: "abc-001",
    out_message: "",
    ret_code: 0);

Use Option A/B whenever output size/type must be controlled.

Return Shape Matrix (What You Actually Get)

DynamicProcedureInvoker chooses result shape from generic type arguments.

No generic type arguments (db.Procedures.MyProc(...))

Main result path:

  • procedure executes via ExecuteNonQuery()
  • if return-value support is enabled and return param is present, that value replaces affected-row count

Final returned object:

  • if no out/ret params were requested: scalar main result (int or return value)
  • if any out/ret/both params were requested: dynamic object with keys

One generic type argument (MyProc<TMain>(...))

Main result type resolution:

  • TMain == IDataReader => returns cached reader (DynamicCachedReader)
  • TMain == DataTable => returns DataTable
  • TMain == List<object> or IEnumerable<object> => list of dynamic rows
  • TMain == List<primitive/string> => converted first-column list
  • TMain == List<complex> => mapped list
  • TMain == primitive/string => scalar converted value
  • TMain == complex class => first row mapped to class (or null)

Final returned object:

  • if no out/ret params were requested: TMain result directly
  • if out/ret params exist: dynamic object containing main result + out params

Important nuance:

  • when out params exist and only one generic type is provided, the result is a dynamic object, not bare TMain.

Two generic type arguments (MyProc<TMain, TOutModel>(...))

This is the preferred pattern when out params are involved.

  • TMain is resolved as above
  • out/main values are packed into a dictionary-like dynamic payload
  • if mapper for TOutModel exists, payload is mapped to TOutModel
  • otherwise fallback is dynamic object

Result Key Names in Out Payload

When out payload is used:

  • main result is stored under procedure method name key (binder.Name)
  • each out/both/ret param is stored under normalized parameter name (without prefix)

Example call:

var res = db.Procedures.sp_Message_SetState<int, MessageSetStateResult>(
    id: "abc-001",
    out_message: new DynamicSchemaColumn { Name = "message", Type = DbType.String, Size = 1024 },
    ret_code: 0);

Expected payload keys before mapping:

  • sp_Message_SetState (main result)
  • message
  • code

Preparing Result Classes Correctly

Use ColumnAttribute to map returned payload keys.

public class MessageSetStateResult
{
    [Column("sp_Message_SetState")]
    public int MainResult { get; set; }

    [Column("message")]
    public string Message { get; set; }

    [Column("code")]
    public int ReturnCode { get; set; }
}

If key names and property names already match, attributes are optional.

Common Patterns

Pattern 1: Main scalar only

int count = db.Procedures.sp_CountMessages<int>();

Pattern 2: Output params + mapped output model

var res = db.Procedures.sp_Message_SetState<int, MessageSetStateResult>(
    id: "abc-001",
    status: 2,
    out_message: new DynamicSchemaColumn { Name = "message", Type = DbType.String, Size = 1024 },
    ret_code: 0);

Pattern 3: Procedure returning dataset as table

DataTable dt = db.Procedures.sp_Message_GetBatch<DataTable>(batchId: 10);

Pattern 4: Procedure returning mapped collection

List<MessageRow> rows = db.Procedures.sp_Message_List<List<MessageRow>>(status: 1);

Pattern 5: Read output dynamically

dynamic res = db.Procedures.sp_Message_SetState(
    id: "abc-001",
    out_message: new DynamicSchemaColumn { Name = "message", Type = DbType.String, Size = 1024 },
    ret_code: 0);

var message = (string)res.message;
var code = (int)res.code;

Troubleshooting Checklist

  • Out value is truncated or null:
    • define output schema explicitly with DynamicSchemaColumn (type + size)
  • Unexpected return object shape:
    • check whether any out/ret/both parameter was passed
    • if yes, expect out payload object unless you used 2-generic mapping variant
  • Mapping to class fails silently (dynamic fallback):
    • ensure output model is mappable and keys match columns/properties
  • Return value not appearing:
    • ensure ret_ parameter is supplied, or enable provider option SupportStoredProceduresResult

Notes

  • Behavior is implemented in DynamORM/DynamicProcedureInvoker.cs.
  • XML examples also appear in DynamORM/DynamicDatabase.cs.