Expand stored procedure docs with result-shape and output parameter guidance

This commit is contained in:
root
2026-02-26 08:30:43 +01:00
parent cf15a898be
commit e95bbd0f67

View File

@@ -2,99 +2,239 @@
Stored procedure support is available through `db.Procedures` when `DynamicDatabaseOptions.SupportStoredProcedures` is enabled.
## Basic Invocation
This page documents actual runtime behavior from `DynamicProcedureInvoker`.
## Invocation Basics
```csharp
var scalar = db.Procedures.sp_Exp_Scalar();
var scalarTyped = db.Procedures.sp_Exp_Scalar<int>();
```
## Schema-Qualified Invocation
Dynamic member chaining builds qualified names:
Schema-qualified naming is built through dynamic chaining:
```csharp
var res = db.Procedures.dbo.reporting.MyProc();
```
This resolves to `dbo.reporting.MyProc`.
Final command name is `dbo.reporting.MyProc`.
## Input, Output, Return, InputOutput Parameters
## Parameter Direction Prefixes
Prefixes in argument names control parameter direction:
Because dynamic `out/ref` is limited, parameter direction is encoded by argument name prefix:
- `out_` for output
- `ret_` for return value
- `both_` for input/output
- `out_` => `ParameterDirection.Output`
- `ret_` => `ParameterDirection.ReturnValue`
- `both_` => `ParameterDirection.InputOutput`
Example:
```csharp
dynamic res = db.Procedures.sp_Test_Scalar_In_Out(
inp: Guid.NewGuid(),
out_outp: Guid.Empty,
ret_Return: 0);
dynamic res = db.Procedures.sp_Message_SetState(
id: "abc-001",
both_state: "pending",
out_message: "",
ret_code: 0);
```
## Using `DynamicSchemaColumn` for Explicit Output Shape
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.
```csharp
var res = db.Procedures.sp_Exp_SomeInputAndOutput<string, ProcResult>(
Name: "G4g4r1n",
out_Result: new DynamicSchemaColumn
dynamic res = db.Procedures.sp_Message_SetState(
id: "abc-001",
out_message: new DynamicSchemaColumn
{
Name = "Result",
Size = 256
Name = "message",
Type = DbType.String,
Size = 1024,
},
ret_Return: 0);
ret_code: 0);
```
## Using `DynamicColumn` for Direction + Value + Schema
## Option B: `DynamicColumn` (recommended for input/output with value)
Use this when you need direction + value + schema in one object.
```csharp
var res = db.Procedures.sp_WithInputOutput(
both_State: new DynamicColumn
dynamic res = db.Procedures.sp_Message_SetState(
both_state: new DynamicColumn
{
ColumnName = "State",
ColumnName = "state",
ParameterDirection = ParameterDirection.InputOutput,
Value = "pending",
Schema = new DynamicSchemaColumn { Name = "State", Size = 32 }
Schema = new DynamicSchemaColumn
{
Name = "state",
Type = DbType.String,
Size = 32,
}
});
```
## Result Shapes
## Option C: Plain value + name prefix
From `DynamicProcedureInvoker` behavior:
- `T == IDataReader`: returns `CachedReader()` result.
- `T == DataTable`: materializes via `ToDataTable(...)`.
- `T == IEnumerable<object>`: dynamic row enumeration.
- `T == IEnumerable<primitive>`: converts first column of each row.
- `T == IEnumerable<class>`: maps rows via mapper cache.
- `T == class`: maps structured result to a class.
Examples:
Quickest form, but type/size inference is automatic.
```csharp
IDataReader rdr = db.Procedures.MyProc<IDataReader>();
DataTable table = db.Procedures.MyProc<DataTable>();
List<int> ids = db.Procedures.MyProc<List<int>>();
List<User> users = db.Procedures.MyProc<List<User>>();
User user = db.Procedures.MyProc<User>();
dynamic res = db.Procedures.sp_Message_SetState(
id: "abc-001",
out_message: "",
ret_code: 0);
```
## Output and Return Value Aggregation
Use Option A/B whenever output size/type must be controlled.
When output and/or return values are used, DynamORM aggregates:
## Return Shape Matrix (What You Actually Get)
- main result
- output parameters
- return value
`DynamicProcedureInvoker` chooses result shape from generic type arguments.
into a dynamic object or mapped class (if a target type is provided).
## 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:
```csharp
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.
```csharp
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
```csharp
int count = db.Procedures.sp_CountMessages<int>();
```
## Pattern 2: Output params + mapped output model
```csharp
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
```csharp
DataTable dt = db.Procedures.sp_Message_GetBatch<DataTable>(batchId: 10);
```
## Pattern 4: Procedure returning mapped collection
```csharp
List<MessageRow> rows = db.Procedures.sp_Message_List<List<MessageRow>>(status: 1);
```
## Pattern 5: Read output dynamically
```csharp
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
- Enable `DynamicDatabaseOptions.SupportStoredProcedures` in options.
- Prefix stripping is automatic in result keys (`out_Result` becomes `Result`).
- Behavior is implemented in `DynamORM/DynamicProcedureInvoker.cs` and XML examples in `DynamORM/DynamicDatabase.cs`.
- Behavior is implemented in `DynamORM/DynamicProcedureInvoker.cs`.
- XML examples also appear in `DynamORM/DynamicDatabase.cs`.