SQL Server
Execute
17 samples, from the least code that works to complete workflows.
What an Execute does
| The call | new ArxProcedureExpression("sp_place_order"), in the pattern of ArxQueryExpression: AddParameter, AddOutParameter<T>, AddInOutParameter<T>; ProcedureType = ArxProcedureType.Function for a function. Never SQL text. AddCursorParameter, AddVariadicParameter and ReturnsCursors are ARX8001: SQL Server has no cursor parameters and no VARIADIC |
| Procedure or function | The caller says which: a procedure runs as EXEC @arx_return = sp @p_name = @p0, …, a function as SELECT * FROM fn(…) (table-valued) or SELECT fn(…) (scalar: ARX finds out on the first call and remembers it). A table-valued function called as a procedure is ARX8001 (SQL Server's error 2809), naming the right ProcedureType; a scalar one runs, because SQL Server's EXEC accepts it, and its value comes back as the RETURN value. A procedure called as a function is ARX5001 (there is no function by that name) |
| Arguments | A procedure's by name (with or without the @) or by position (positional first); a function's by position only. Each is a typed parameter through the same value path as Create (dates, JSON, selection sets, lookups). A null whose type matters needs it: AddParameter(name, type, null) |
| What comes back | ArxExecuteResult: Outputs (OUTPUT values as their declared types, and the procedure's RETURN value as Outputs[ArxMsSqlDbService.RETURN_VALUE]), ResultSets (each SELECT the procedure runs, by position, or a function's rows as ResultSets[0]), Messages (PRINT and RAISERROR of severity 10 or less, in order). Value<T>() reads a function's single value |
| OUT and INOUT | SQL Server has one kind, OUTPUT, which is both: an OUT starts as NULL. ARX sizes each to hold any value of its declared type (nvarchar(max), decimal(38, 10), …), so nothing is cut short |
| Tables from a procedure | Every SELECT the procedure runs is a result set, read by position (ResultSets[0], [1] …) in one round trip. OUTPUT values arrive after the last one |
| Paging | PageInfo, as on a query, three ways: the procedure pages itself (PagingParameters: ARX fills its page arguments and reads its total), ARX pages a function's rows (OFFSET … FETCH NEXT, the total in one round trip), ARX pages a procedure's result set (on the client: earlier rows read and skipped, the rest counted; every row crosses the network). The total is opt-in |
| Transactions | ARX's own call runs outside any transaction, so a procedure may begin and commit its own; in an ArxTransaction or a batch scope, a procedure that commits or rolls back the caller's transaction is ARX8001 (SQL Server's error 266: the transaction count changed), and the transaction is aborted |
| Errors the procedure raises | THROW and RAISERROR of severity 11 or more are ARX3005 RaisedByDatabase, with the procedure's message and its error number as NativeCode (50101 for THROW 50101, …), so the application can branch on its own numbers |
| Security | A procedure, function or parameter name carrying SQL is ARX1017, refused before the database; values are parameters |
| Names in the samples | Procedures sp_, functions fn_ (views vw_) |
| Without a catalog | Needs no catalog and reads nothing first: a service created with only the connection string calls procedures and functions as any other |
Reference
Error codes an Execute can raise
| Code | When |
|---|---|
| ARX1001 InvalidArgument | A positional argument after a named one; OUT or INOUT parameters on a function; an OUT without a type; AddOrder on a procedure; PageInfo with nothing to page or below 1; PagingParameters without PageInfo; PagedResultSet naming no result set |
| ARX1002, ARX1003, ARX1004 | No routine name; an invalid name (three parts, a typo); a parameter given twice |
| ARX1007, ARX1013 | A value type ARX can't write; default(DateTime) |
| ARX1008 InvalidConnection | A connection with an open transaction, given without that transaction |
| ARX1017 SuspectedInjection | A procedure, function, parameter or paging name carrying SQL |
| ARX2003 MultipleRecordsMatched | Value<T>() on several rows |
| ARX3001–3004 | A constraint the procedure broke (kept from the database) |
| ARX3005 RaisedByDatabase | The procedure refused: THROW, or RAISERROR of severity 11 to 19 (error numbers 50000 and up) |
| ARX4003 InvalidValueFormat | An argument that doesn't convert to the parameter's type ('abc' for an int) |
| ARX4004 DataTypeMismatch | Arguments of a type the parameter can't take; too many arguments (8144); a named argument the procedure doesn't have (8145); a required parameter left out (201) |
| ARX5001 UndefinedEntity | No procedure or function by that name (2812, 208) |
| ARX5003 SchemaMismatch | An OUT value of another type than declared, that can't be widened |
| ARX6001 PermissionDenied | No EXECUTE permission |
| ARX7003 Timeout | CommandTimeout exceeded (SQL Server has no statement timeout of its own: the client's applies) |
| ARX8001 NotSupported | A table-valued function called as a procedure (2809); a procedure that commits or rolls back inside an ARX transaction or a scope (266); cursor parameters, VARIADIC, ReturnsCursors (before the database); named arguments or result columns on a function |
The samples
Basics
The least code that works.
1Call a stored procedureProgressiveCalling a stored procedure, in three scenarios: named arguments; positional arguments; a parameter left out, so the procedure's default is used. The call is described with ArxProcedureExpression, the way a query is described with ArxQueryExpression; ARX sends EXEC with every value as a parameter.
OUT and INOUT values, in two scenarios: an INOUT goes in and comes back (here filled in by the procedure when it arrives as NULL), and OUT values come back typed in Outputs. SQL Server has one kind, OUTPUT, which is both: ARX sends an OUT as an OUTPUT parameter that starts as NULL. Each is declared with its .NET type, and ARX sizes it to hold any value of that type.
Calling a function, in three scenarios: one value (Value<T>()); rows (ResultSets[0]); and an effect, which on SQL Server is a procedure's job. A function is the same ArxProcedureExpression with Function: ARX sends SELECT * FROM fn(…) for a table-valued function and SELECT fn(…) for a scalar one, instead of EXEC. To filter, order or page a function's rows like a table, Retrieve reads it as a row source (RetrieveMultiple's RealCase6, Advanced8).
A procedure that returns tables, in two scenarios. A SQL Server procedure returns a table by selecting it: each SELECT it runs is a result set. ARX runs the call in one round trip and returns the result sets by position (ResultSets[0], ResultSets[1] …), in the order the procedure sends them.
Real cases
What an application does: a small CRM and product catalogue.
1Place an order through a procedureProgressivePlacing an order through a procedure, in two scenarios: sp_place_order writes the order and its lines (from the products' current prices) in one call and returns the new id and the total; the id is then used like any other (Retrieve reads the order back). The application doesn't repeat the pricing rule the procedure owns.
An account page built from one call: sp_account_report returns three tables (the contacts, the orders, a summary row), read together in one round trip, each by its position: the order of the procedure's SELECTs.
Paging what a procedure or function returns, the three ways, in four scenarios. Each sets PageInfo, as a query does, and gets back a result set with CurrentPage, HasNextPage and, when asked, the total. The total is opt-in: ask for it once (page 1), as for RetrieveMultiple.
- The routine pages itself (best for large data: the database produces only the page). ARX fills its page arguments from
PageInfoand reads its total from an OUT parameter. - ARX pages a function's rows:
OFFSET/FETCH NEXTand the total in one round trip. Cheap for an inline table-valued function (SQL Server expands it, so the order and the limit reach the index); a multi-statement function fills its whole table first. - ARX pages a table the procedure returns (a result set): SQL Server has no cursor to move, so ARX reads past the earlier rows, keeps one page and one row more, and counts the rest. Every row crosses the network.
- Which to choose.
Business rules the database enforces, in three scenarios. sp_reserve_credit refuses an account on hold (THROW 50101) and an amount over the credit limit (THROW 50102). Both arrive as ARX3005 RaisedByDatabase with the procedure's message and its error number as NativeCode, so the application can tell "the database said no" from "something broke", and branch on the procedure's own number.
A nightly job that commits as it goes, in two scenarios. sp_recalculate_totals recalculates order totals in batches and commits after each, so a failure late in the run keeps the batches already done, and reports its progress. ARX's own call runs outside any transaction, which a procedure that commits needs (inside one, SQL Server ends the caller's transaction at the procedure's COMMIT and reports error 266, a changed transaction count); inside an ARX transaction or a batch scope the same call is refused with ARX8001 and the reason.
The messages a procedure sends while it runs, in two scenarios: PRINT and RAISERROR of severity 10 or less (information, not errors; SQL Server reports their severity as 0) come back in Messages, in order, for the application to show or log as it chooses (ARX never writes them to a log); CollectMessages = false leaves them out.
The errors an Execute call can raise, turned into messages, in two scenarios: what the database refuses (a procedure that doesn't exist, arguments of the wrong type, a function called as a procedure, a timeout) and what ARX refuses before it (a positional argument after a named one, an OUT parameter without a type, a cursor parameter).
Catching an SQL injection attempt on Execute, in three scenarios. A routine name or a parameter name that carries SQL is refused with ARX1017 SuspectedInjection before the database. SQL in a value is a parameter: it's stored as text and runs nothing. Execute never takes SQL text.
Advanced cases
Dependency injection, transactions, batch scopes and setting the service up.
1Execute in an ARX transactionAdvancedExecute inside an ARX transaction, in two scenarios: a Create and a procedure in one transaction, the procedure's writes read back inside it, and everything gone after a rollback; and a business error from the procedure aborting the transaction, so the next call is refused (ARX1015) until the application rolls back.
Execute in a batch scope, in one scenario: an import of three orders through the procedure, one transaction. Each call has its own savepoint (asked for with Savepoints = true), so the order the procedure refuses (a credit limit) is undone alone and the import continues; the others are committed together at the end.
Exporting everything a procedure returns, page by page, in one scenario: sp_products_page pages itself (the first way of paging), the export asks for the total on the first page only, and reads pages until HasNextPage is false. Each page is one call, so the memory is one page's.
A security check around Execute (SecurityGuard, in the project root), in three scenarios: a report run through an allowed list of procedures passes; an endpoint that runs whatever procedure the request names is probed, and the attacker is blocked after three attempts; and a false positive (an old client sending "account report (v1)") is triaged: support unblocks the user and the endpoint maps the old name.
Execute on a service whose schema is preloaded and Strict, in three scenarios: a procedure and a function called as usual, because Strict covers tables and views, not routines; a function read as a row source, with the routine's own columns; and the same service still refusing a table column that wasn't preloaded.