# 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 result - `false`: invoker keeps `ExecuteNonQuery()` 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 ```csharp var scalar = db.Procedures.sp_Exp_Scalar(); var scalarTyped = db.Procedures.sp_Exp_Scalar(); ``` Schema-qualified naming is built through dynamic chaining: ```csharp 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: ```csharp 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. ```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); ``` ## Option B: `DynamicColumn` (recommended for input/output with value) Use this when you need direction + value + schema in one object. ```csharp 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. ```csharp 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(...)`) Main result type resolution: - `TMain == IDataReader` => returns cached reader (`DynamicCachedReader`) - `TMain == DataTable` => returns `DataTable` - `TMain == List` or `IEnumerable` => list of dynamic rows - `TMain == List` => converted first-column list - `TMain == List` => 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(...)`) 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( 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(); ``` ## Pattern 2: Output params + mapped output model ```csharp var res = db.Procedures.sp_Message_SetState( 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(batchId: 10); ``` ## Pattern 4: Procedure returning mapped collection ```csharp List rows = db.Procedures.sp_Message_List>(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 - ensure provider option `SupportStoredProceduresResult` matches your DB behavior - Procedure call errors on non-SQL Server providers: - set `SupportStoredProceduresResult = false` - return status via explicit `out_` parameters instead of return-value semantics ## Notes - Behavior is implemented in `DynamORM/DynamicProcedureInvoker.cs`. - XML examples also appear in `DynamORM/DynamicDatabase.cs`.