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.Outputret_=>ParameterDirection.ReturnValueboth_=>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.
Option A: DynamicSchemaColumn (recommended for output-only)
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);
Option B: DynamicColumn (recommended for input/output with value)
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 (
intor 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=> returnsDataTableTMain == List<object>orIEnumerable<object>=> list of dynamic rowsTMain == List<primitive/string>=> converted first-column listTMain == List<complex>=> mapped listTMain == primitive/string=> scalar converted valueTMain == complex class=> first row mapped to class (ornull)
Final returned object:
- if no out/ret params were requested:
TMainresult 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.
TMainis resolved as above- out/main values are packed into a dictionary-like dynamic payload
- if mapper for
TOutModelexists, payload is mapped toTOutModel - 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)messagecode
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)
- define output schema explicitly with
- 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 optionSupportStoredProceduresResult
- ensure
Notes
- Behavior is implemented in
DynamORM/DynamicProcedureInvoker.cs. - XML examples also appear in
DynamORM/DynamicDatabase.cs.