7.6 KiB
Stored Procedures
Stored procedure support is available through db.Procedures when DynamicDatabaseOptions.SupportStoredProcedures is enabled.
This page documents actual runtime behavior from DynamicProcedureInvoker.
SupportStoredProceduresResult and Provider Differences
DynamicProcedureInvoker can treat the procedure "main result" as either:
- affected rows from
ExecuteNonQuery() - provider return value parameter
This behavior is controlled by DynamicDatabaseOptions.SupportStoredProceduresResult.
true: if a return-value parameter is present, invoker uses that value as main resultfalse: invoker keepsExecuteNonQuery()result (safer for providers that do not expose SQL Server-like return value behavior)
Why this matters:
- SQL Server commonly supports procedure return values in this style
- some providers (for example Oracle setups) do not behave the same way
- forcing return-value extraction on such providers can cause runtime errors or invalid result handling
If procedures fail on non-SQL Server providers, first disable SupportStoredProceduresResult and rely on explicit out_ parameters for status/result codes.
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 - ensure provider option
SupportStoredProceduresResultmatches your DB behavior
- ensure
- Procedure call errors on non-SQL Server providers:
- set
SupportStoredProceduresResult = false - return status via explicit
out_parameters instead of return-value semantics
- set
Notes
- Behavior is implemented in
DynamORM/DynamicProcedureInvoker.cs. - XML examples also appear in
DynamORM/DynamicDatabase.cs.