PostgreSQL · Execute
Call a function: a value, rows, an effect
Calling a function, in three scenarios: one value (Value<T>()); rows (ResultSets[0]); a function run for its effect (void: no rows). A function is the same ArxProcedureExpression with Function: ARX sends SELECT * FROM instead of CALL. To filter, order or page a function's rows like a table, Retrieve reads it as a row source (RetrieveMultiple's RealCase6, Advanced8).
Set up for this page
The scenarios build on the sample application: SampleContext.cs (the dbContext every sample is given); SampleOutput.cs (Step and Show, which print); SampleContext.Application.cs (dbContext.Service, the application's ARX service); SampleCatalog.cs (the tables' ids, keys, lookups and display names).
Before scenario 1 the sample runs this, so the service and the rows the scenarios use are there:
var account = new ArxEntity("account");
account["name"] = "Function Fabrics";
account["account_number"] = "ACC-E301";
var accountId = await service.CreateAsync(account);
foreach (var (first, title) in new[] { ("Fran", "Buyer"), ("Finn", "Engineer"), ("Faye", "Director") })
{
var contact = new ArxEntity("contact");
contact["first_name"] = first;
contact["last_name"] = "Fn-E301";
contact["job_title"] = title;
contact["account_id"] = new ArxEntityReference("account", accountId);
await service.CreateAsync(contact);
}1 One value
fn_contact_count(p_account_id) returns a bigint.
Already in scope: service (ArxPgDbService), accountId (ArxRecordId)
var function = new ArxProcedureExpression("fn_contact_count") { ProcedureType = ArxProcedureType.Function };
function.AddParameter("p_account_id", accountId.AsInt64());
var count = (await service.ExecuteAsync(function)).Value<long>();
Console.WriteLine($"{count} contacts");Check it in the database
Run it in any SQL tool connected to the database, after the code has run.
SELECT fn_contact_count(a.id)
FROM account a
WHERE a.account_number = 'ACC-E301';Expect: 3.
2 Rows
fn_contacts_named(p_last_name) returns a table: its rows are the first result set.
Already in scope: service (ArxPgDbService)
var function = new ArxProcedureExpression("fn_contacts_named") { ProcedureType = ArxProcedureType.Function };
function.AddParameter("Fn-E301");
var rows = (await service.ExecuteAsync(function)).ResultSets[0];
Console.WriteLine(string.Join(", ", rows.Entities.Select(x => $"{x["first_name"]} ({x["job_title"]})")));Check it in the database
Run it in any SQL tool connected to the database, after the code has run.
SELECT first_name, job_title
FROM fn_contacts_named('Fn-E301');Expect: 3 rows: Faye Director, Finn Engineer, Fran Buyer.
3 A function run for its effect
fn_touch_account returns void: it's run for what it does, and returns no rows.
Already in scope: service (ArxPgDbService), accountId (ArxRecordId)
var function = new ArxProcedureExpression("fn_touch_account") { ProcedureType = ArxProcedureType.Function };
function.AddParameter(accountId.AsInt64());
var result = await service.ExecuteAsync(function);
Console.WriteLine($"rows returned: {result.ResultSets[0].Entities.Count}");Check it in the database
Run it in any SQL tool connected to the database, after the code has run.
SELECT modified_on IS NOT NULL AS touched
FROM account
WHERE account_number = 'ACC-E301';Expect: t.
Run the complete sample
The code above comes from one program, built in a fresh project and run against the database. To run it yourself, here is every file and step.
The program, its files and how to run it
Run it locally
- .NET SDK: .NET SDK 10.0.100 or later 10.0.x (target
net10.0). - Create the project:
Terminal dotnet new console -n ArxSample -f net10.0 cd ArxSample - Install the packages:
Terminal dotnet add package ARX.SDK.PostgreSql --version 1.0.0-rc.1 - Database: a PostgreSQL 16 (tested with 16.15) database with the sample schema.
- Configuration: set
ARX_SAMPLES_POSTGRESQLto its connection string. Nothing is hard-coded. - Source code: add the files of the sample application this sample uses (
SampleContext.cs,SampleOutput.cs,SampleContext.Application.cs,SampleCatalog.cs), then replaceProgram.csand add the other file below, with the file names shown. - Run it:
Terminal dotnet run - Expected output: below.
The files
Runs the sample. Generated for this page; it reads the connection string from the environment.
using ArxSamples.PostgreSql;
using ArxSamples.PostgreSql.Operations.Execute.Basics;
// The connection string comes from the environment, never from the code (see "Configure the connection").
var connectionString = Environment.GetEnvironmentVariable("ARX_SAMPLES_POSTGRESQL")
?? throw new InvalidOperationException("Set the ARX_SAMPLES_POSTGRESQL environment variable to your PostgreSQL connection string.");
await new Basic3_CallAFunction().RunAsync(new SampleContext(connectionString));
The sample, as it is in the repository.
using ARX.SDK.Query;
using ARX.SDK.Services;
using static ArxSamples.PostgreSql.SampleOutput;
namespace ArxSamples.PostgreSql.Operations.Execute.Basics;
/// <summary>
/// Calling a function, in three scenarios: one value (<c>Value<T>()</c>); rows (<c>ResultSets[0]</c>); a function
/// run for its effect (<c>void</c>: no rows). A function is the same <see cref="ArxProcedureExpression"/> with
/// <see cref="ArxProcedureType.Function"/>: ARX sends <c>SELECT * FROM</c> instead of <c>CALL</c>. To filter, order or page a
/// function's rows like a table, Retrieve reads it as a row source (RetrieveMultiple's RealCase6, Advanced8).
/// </summary>
public sealed class Basic3_CallAFunction : ISample
{
public string Name => "execute-basics-3-call-a-function";
public string Title => "Call a function: a value, rows, an effect";
public async Task RunAsync(SampleContext dbContext)
{
var account = new ArxEntity("account");
account["name"] = "Function Fabrics";
account["account_number"] = "ACC-E301";
var accountId = await dbContext.Service.CreateAsync(account);
foreach (var (first, title) in new[] { ("Fran", "Buyer"), ("Finn", "Engineer"), ("Faye", "Director") })
{
var contact = new ArxEntity("contact");
contact["first_name"] = first;
contact["last_name"] = "Fn-E301";
contact["job_title"] = title;
contact["account_id"] = new ArxEntityReference("account", accountId);
await dbContext.Service.CreateAsync(contact);
}
await Scenario1_OneValueAsync(dbContext, accountId);
await Scenario2_RowsAsync(dbContext);
await Scenario3_AnEffectAsync(dbContext, accountId);
}
/// <summary>1. <c>fn_contact_count(p_account_id)</c> returns a bigint.</summary>
private static async Task Scenario1_OneValueAsync(SampleContext dbContext, ArxRecordId accountId)
{
Step("1. One value");
var function = new ArxProcedureExpression("fn_contact_count") { ProcedureType = ArxProcedureType.Function };
function.AddParameter("p_account_id", accountId.AsInt64());
var count = (await dbContext.Service.ExecuteAsync(function)).Value<long>();
Show($"{count} contacts");
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT fn_contact_count(a.id)
* FROM account a
* WHERE a.account_number = 'ACC-E301';
* -- Expect: 3.
* ===========================================================
*/
}
/// <summary>2. <c>fn_contacts_named(p_last_name)</c> returns a table: its rows are the first result set.</summary>
private static async Task Scenario2_RowsAsync(SampleContext dbContext)
{
Step("2. Rows");
var function = new ArxProcedureExpression("fn_contacts_named") { ProcedureType = ArxProcedureType.Function };
function.AddParameter("Fn-E301");
var rows = (await dbContext.Service.ExecuteAsync(function)).ResultSets[0];
Show(string.Join(", ", rows.Entities.Select(x => $"{x["first_name"]} ({x["job_title"]})")));
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT first_name, job_title
* FROM fn_contacts_named('Fn-E301');
* -- Expect: 3 rows: Faye Director, Finn Engineer, Fran Buyer.
* ===========================================================
*/
}
/// <summary>3. <c>fn_touch_account</c> returns <c>void</c>: it's run for what it does, and returns no rows.</summary>
private static async Task Scenario3_AnEffectAsync(SampleContext dbContext, ArxRecordId accountId)
{
Step("3. A function run for its effect");
var function = new ArxProcedureExpression("fn_touch_account") { ProcedureType = ArxProcedureType.Function };
function.AddParameter(accountId.AsInt64());
var result = await dbContext.Service.ExecuteAsync(function);
Show($"rows returned: {result.ResultSets[0].Entities.Count}");
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT modified_on IS NOT NULL AS touched
* FROM account
* WHERE account_number = 'ACC-E301';
* -- Expect: t.
* ===========================================================
*/
}
}
Expected output
What dotnet run printed when the sample was verified, on a freshly created sample schema. Ids differ when the tables already hold rows.
> 1. One value
3 contacts
> 2. Rows
Faye (Director), Finn (Engineer), Fran (Buyer)
> 3. A function run for its effect
rows returned: 0