Programmable object
What are programmable objects ?
Programmable objects are used to execute database stored procedures and functions on the server-side.
Programmable object properties
Name : name of the programmable object, used to name the corresponding C# class and its interface.
Description : description of the programmable object
Type : type of the programmable object
Name Description Scalar functionA user-defined SQL function that can take parameters an return a single value. Table functionA user-defined SQL function that can take parameters and return multiple records composed of columns. Stored procedureA precompiled set of SQL statements that can take parameters. SQL Server stored procedures can return a return code and the result of their last SELECTstatement while PostgreSQL and Oracle stored procedures are not designed to return values (except via theInputOutputandOutputparameters).Return data .Net type : type of the data returned by the programmable object. For table functions and stored procedures, the specified type will be used as the generic type of the returned collection.
Warning
Scalar functions can only return simple types (int, string, ...). If you need to return a complex type, set the programmable object as a table function.
Complex types cannot be interfaces. Their property names must match (case-insensitive) the returned column names.Parameters : list of the parameters that must be passed to the SQL function or stored procedure.
Name : name of the parameter as defined in the database
Description : description of the parameter
Direction : direction of the parameter
Name Description InputParameter whose value is passed to the programmable object and cannot be changed. InputOutputParameter whose value is passed to the programmable object and can be updated by the programmable object. OutputParameter whose value can only be updated by the programmable object. ReturnValueParameter whose value is the code returned by a Microsoft SQL Server stored procedure. Position : order of the parameter, see Parameter order
Size : maximum size in characters for varying-length parameters
Note
If the programmable object name or its configuration per database engine are correctly set for your development database, Neos can automatically create parameters from the database using the
Add from databasebutton. The parameter positions follow the declaration order in the database.Configuration per engine : indicates how the function or stored procedure is called in the different supported database engines. See Configuration per database engine.
Configuration per database engine
Every programmable object has a generic configuration, named All databases, which applies to every database engine. It is always present and cannot be deleted.
You can add an optional configuration for a specific engine (Oracle, PostgreSQL or SQL Server). A configuration for an engine fully replaces the generic configuration for that engine: values are never inherited field by field.
In Neos Studio, the Configuration per engine tab shows the configurations as cards. When you add a card for an engine, it starts with a copy of the generic values, adjusted to the engine (see the binding modes below). Once created, it is independent of the generic card.
Each configuration has the following properties :
- Database type :
All databases(generic configuration),Oracle,PostgreSQLorSQL Server - Name : name of the stored procedure or SQL function in the database
- Schema : database schema to which the SQL function or stored procedure belongs
- Parameter binding mode : how the parameters are passed when calling the object
Name and schema
The same rule applies to every configuration :
- If Name is empty, the name of the programmable object is used.
- If Schema is filled, it is used.
- If Schema is empty and Name is empty, the default schema of the engine is used :
dboon SQL Server,publicon PostgreSQL and no schema on Oracle. - If Schema is empty and Name is filled, no schema is used : the object is called by its name only and the database finds its schema.
Neos Studio shows the resulting Name used at runtime. Examples for a programmable object named GetOrders :
| Name | Schema | Object called |
|---|---|---|
| (empty) | (empty) | dbo.GetOrders on SQL Server, "public"."GetOrders" on PostgreSQL, GetOrders on Oracle |
| (empty) | sales |
sales.GetOrders |
fn_orders |
(empty) | fn_orders |
fn_orders |
sales |
sales.fn_orders |
Note
You only need to configure a name or a schema when your database uses a custom schema or when the name of the programmable object does not match the name of the SQL function or stored procedure in the database.
Parameter binding mode
The binding mode defines how the parameters are passed to the database.
| Mode | Description |
|---|---|
Default |
Only for the generic configuration of a function. Binds by name on PostgreSQL and Oracle, and by position on SQL Server. |
Name |
Parameters are passed with their name. The parameter names must match exactly the names declared in the database, including case on PostgreSQL. |
Position |
Parameters are passed in the order of their Position property. |
The allowed values depend on the object type and the database engine :
| Object | Configuration | Allowed modes | Reason |
|---|---|---|---|
| Stored procedure | Every configuration | Name |
The database providers bind stored procedure parameters by name. |
| Scalar function or table function | Generic (All databases) |
Default, Position |
Name is forbidden because the generic configuration also applies to SQL Server. |
| Scalar function or table function | SQL Server | Position |
SQL Server does not support named arguments when calling a function. |
| Scalar function or table function | PostgreSQL or Oracle | Name, Position |
Both engines support the named notation for functions. |
New functions start with the Default mode. The allowed values are stored in the metadata and checked, they are not recomputed at generation. When you add a card in Neos Studio, Default becomes Name on a PostgreSQL or Oracle card, and a SQL Server card always receives Position.
In Name mode, Neos calls functions with the named notation :
- PostgreSQL :
"Param" => @Param(names are case-sensitive) - Oracle :
Param => :Param
Parameter order
Each parameter has a Position. Positions are unique for the programmable object, contiguous and start at 1.
- The position follows the order in which the parameters are declared in the database, output parameters included. The
Add from databasebutton fills it that way. The direction does not impose any order. - It sets the order of the arguments of the C# method that calls the programmable object, and the order of the arguments of the SQL call in
Positionmode. It is the same for every database engine. - In
Positionmode, the parameters that an engine does not pass to a function are skipped without shifting the others: output parameters on every engine, and input-output parameters on Oracle. - Stored procedures are bound by name, so the order has no effect on their call.
- In Neos Studio, the parameter list is sorted by position. Reorder parameters by drag and drop, or by entering a position, and the other positions are renumbered.
In the C# method that calls the programmable object (ExecuteAsync), Input and InputOutput parameters are the arguments of the method, and Output and InputOutput parameters are properties of its result, each in the order of their positions.
Calling the programmable object from the server
Programmable objects can only be called server-side.
An interface named after each programmable object can be injected where needed.
Scalar functions
Scalar function programmable objects are called using the ExecuteAsync method that takes the configured Input parameters. They return a simple value of the configured type.
Example with a scalar function programmable object that uses a string Input parameter and returns an integer :
public class CallProgrammableObject
{
private readonly IFunctionGetId _functionGetId;
public CallProgrammableObject(IFunctionGetId functionGetId)
{
_functionGetId = functionGetId;
}
public async Task ExecuteAsync()
{
int id = await _functionGetId.ExecuteAsync("Apple");
}
}
Table functions
Table function programmable objects are called in the same way as scalar functions. They return a list of records of the configured type.
Example with a table function programmable object that uses an integer Input parameter and returns a record as a data object :
public class CallProgrammableObject
{
private readonly IFunctionGetProduct _functionGetProduct;
public CallProgrammableObject(IFunctionGetProduct functionGetProduct)
{
_functionGetProduct = functionGetProduct;
}
public async Task ExecuteAsync()
{
IEnumerable<GetProductResult> products = await _functionGetProduct.ExecuteAsync(1);
GetProductResult? product = products.FirstOrDefault();
}
}
Stored procedures
Table stored procedure programmable objects are called using the ExecuteAsync method that takes the configured Input and InputOutput parameters. They return an object that contains :
- updated
InputOutputandOutputparameter values - a return code (for SQL Server stored procedures only)
- return data as a list of records of the configured type (for SQL Server stored procedures only)
Warning
PostgreSQL only supports output parameters in stored procedures starting with version 15
Example with a stored procedure programmable object that uses an integer Input parameter, a string InputOutput parameter and a double Output parameter. It updates the string InputOutput parameter and sets the double Output parameter. In a SQL Server database, it returns a code as well as a record as a data object (last SELECT statement) :
public class CallProgrammableObject
{
private readonly IStoredProcedureUpdateProduct _storedProcedureUpdateProduct;
public CallProgrammableObject(IStoredProcedureUpdateProduct storedProcedureUpdateProduct)
{
_storedProcedureUpdateProduct = storedProcedureUpdateProduct;
}
public async Task ExecuteAsync()
{
StoredProcedureUpdateProductResult result = await _storedProcedureUpdateProduct.ExecuteAsync(1, "Apple");
string updatedName = result.Name;
double newPrice = result.NewPrice;
// SQL Server only
int? returnCode = result.ReturnCode;
IEnumerable<GetProductResult> products = result.ReturnData;
GetProductResult? product = products.FirstOrDefault();
}
}
Known issues, limitations, and workarounds
- Functions cannot use output parameters. Whatever the binding mode, output parameters are never passed to a function, and input-output parameters are not passed on Oracle. In
Positionmode, they are skipped without shifting the other positions.- PostgreSQL allows to pass output parameters to a function, but these parameters are used to define the columns of the records returned by the function. Use a Neos data object as the return type for these functions.
- Oracle allows to pass output and input/output parameters to a function, but you cannot use this kind of functions in SQL expressions. You can only use them in PL/SQL statements.
- On Oracle, a scalar function is called in a PL/SQL block, so it can modify data (
INSERT,UPDATE,DELETE). Its return type must be a string or achar, a number, a boolean, a date or a time (DateTime,DateTimeOffset,DateOnly,TimeOnly), aGuid, a byte array or an enumeration. A string or a byte array is received as a LOB, so aCLOBor aBLOBvalue is returned whole. - On Oracle, a table function is called in a query (
SELECT * FROM TABLE(...)), where Oracle forbids modifying data (errorORA-14551). Use a stored procedure with output parameters instead. - Getting records from cursors is currently not supported by programmable objects.