PostgreSQL · Execute
Handle Execute errors
The errors an Execute call can raise, turned into messages, in two scenarios: what the database refuses (a routine that doesn't exist with these argument types, 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, paging with nothing to page).
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).
1 What the database refuses
Mapped from PostgreSQL's errors.
Already in scope: service (ArxPgDbService)
await TryAsync("a procedure that doesn't exist", () => service.ExecuteAsync(new ArxProcedureExpression("sp_no_such_procedure")));
var wrongTypes = new ArxProcedureExpression("sp_rename_account");
wrongTypes.AddParameter("p_account_id", Guid.NewGuid());
wrongTypes.AddParameter("p_name", "x");
await TryAsync("the wrong argument types", () => service.ExecuteAsync(wrongTypes));
var function = new ArxProcedureExpression("fn_contact_count");
function.AddParameter(1L);
await TryAsync("a function called as a procedure", () => service.ExecuteAsync(function));
var slow = new ArxProcedureExpression("sp_slow");
slow.AddParameter("p_seconds", 3.0);
await TryAsync("a timeout of 1 second", () => service.ExecuteAsync(slow, new ArxExecuteOptions { CommandTimeout = 1 }));Defined once in this sample.
private static async Task TryAsync(string label, Func<Task> call)
{
try
{
await call();
Console.WriteLine($"{label}: no error");
}
catch (ArxException error)
{
Console.WriteLine($"{label}: {error.Code} ({(int)error.Code}), {error.Category}");
}
}Check it in the database
Run it in any SQL tool connected to the database, after the code has run.
SELECT proname, prokind
FROM pg_proc
WHERE proname IN ('sp_no_such_procedure', 'sp_rename_account', 'fn_contact_count', 'sp_slow')
ORDER BY proname;Expect: 3 rows: fn_contact_count f, sp_rename_account p, sp_slow p (no sp_no_such_procedure).
2 What ARX refuses first
Refused before any connection opens.
Already in scope: service (ArxPgDbService)
var mixed = new ArxProcedureExpression("sp_rename_account");
mixed.AddParameter("p_account_id", 1L);
mixed.AddParameter("positional after a named one");
await TryAsync("a positional argument after a named one", () => service.ExecuteAsync(mixed));
var untyped = new ArxProcedureExpression("sp_account_totals");
untyped.Parameters.Add(new ArxProcedureParameter("p_orders", null, null, ArxProcedureParameterDirection.Out));
await TryAsync("an OUT parameter without a type", () => service.ExecuteAsync(untyped));
var nothingToPage = new ArxProcedureExpression("sp_rename_account") { PageInfo = new ArxPagingInfo { Count = 10 } };
await TryAsync("paging a procedure that returns no table", () => service.ExecuteAsync(nothingToPage));Defined once in this sample.
private static async Task TryAsync(string label, Func<Task> call)
{
try
{
await call();
Console.WriteLine($"{label}: no error");
}
catch (ArxException error)
{
Console.WriteLine($"{label}: {error.Code} ({(int)error.Code}), {error.Category}");
}
}Check it in the database
Run it in any SQL tool connected to the database, after the code has run.
SELECT count(*)
FROM account
WHERE id = 1 AND name = 'positional after a named one';Expect: 0: nothing reached the database.
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.RealCases;
// 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 RealCase7_HandleErrors().RunAsync(new SampleContext(connectionString));
The sample, as it is in the repository.
using ARX.SDK;
using ARX.SDK.Query;
using ARX.SDK.Services;
using static ArxSamples.PostgreSql.SampleOutput;
namespace ArxSamples.PostgreSql.Operations.Execute.RealCases;
/// <summary>
/// The errors an Execute call can raise, turned into messages, in two scenarios: what the database refuses (a routine
/// that doesn't exist with these argument types, 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, paging with nothing to page).
/// </summary>
public sealed class RealCase7_HandleErrors : ISample
{
public string Name => "execute-real-7-handle-errors";
public string Title => "Handle Execute errors";
public async Task RunAsync(SampleContext dbContext)
{
await Scenario1_WhatTheDatabaseRefusesAsync(dbContext);
await Scenario2_WhatARXRefusesFirstAsync(dbContext);
}
private static async Task TryAsync(string label, Func<Task> call)
{
try
{
await call();
Show($"{label}: no error");
}
catch (ArxException error)
{
Show($"{label}: {error.Code} ({(int)error.Code}), {error.Category}");
}
}
/// <summary>1. Mapped from PostgreSQL's errors.</summary>
private static async Task Scenario1_WhatTheDatabaseRefusesAsync(SampleContext dbContext)
{
Step("1. What the database refuses");
await TryAsync("a procedure that doesn't exist", () => dbContext.Service.ExecuteAsync(new ArxProcedureExpression("sp_no_such_procedure")));
var wrongTypes = new ArxProcedureExpression("sp_rename_account");
wrongTypes.AddParameter("p_account_id", Guid.NewGuid());
wrongTypes.AddParameter("p_name", "x");
await TryAsync("the wrong argument types", () => dbContext.Service.ExecuteAsync(wrongTypes));
var function = new ArxProcedureExpression("fn_contact_count");
function.AddParameter(1L);
await TryAsync("a function called as a procedure", () => dbContext.Service.ExecuteAsync(function));
var slow = new ArxProcedureExpression("sp_slow");
slow.AddParameter("p_seconds", 3.0);
await TryAsync("a timeout of 1 second", () => dbContext.Service.ExecuteAsync(slow, new ArxExecuteOptions { CommandTimeout = 1 }));
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT proname, prokind
* FROM pg_proc
* WHERE proname IN ('sp_no_such_procedure', 'sp_rename_account', 'fn_contact_count', 'sp_slow')
* ORDER BY proname;
* -- Expect: 3 rows: fn_contact_count f, sp_rename_account p, sp_slow p (no sp_no_such_procedure).
* ===========================================================
*/
}
/// <summary>2. Refused before any connection opens.</summary>
private static async Task Scenario2_WhatARXRefusesFirstAsync(SampleContext dbContext)
{
Step("2. What ARX refuses first");
var mixed = new ArxProcedureExpression("sp_rename_account");
mixed.AddParameter("p_account_id", 1L);
mixed.AddParameter("positional after a named one");
await TryAsync("a positional argument after a named one", () => dbContext.Service.ExecuteAsync(mixed));
var untyped = new ArxProcedureExpression("sp_account_totals");
untyped.Parameters.Add(new ArxProcedureParameter("p_orders", null, null, ArxProcedureParameterDirection.Out));
await TryAsync("an OUT parameter without a type", () => dbContext.Service.ExecuteAsync(untyped));
var nothingToPage = new ArxProcedureExpression("sp_rename_account") { PageInfo = new ArxPagingInfo { Count = 10 } };
await TryAsync("paging a procedure that returns no table", () => dbContext.Service.ExecuteAsync(nothingToPage));
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT count(*)
* FROM account
* WHERE id = 1 AND name = 'positional after a named one';
* -- Expect: 0: nothing reached the database.
* ===========================================================
*/
}
}
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. What the database refuses
a procedure that doesn't exist: UndefinedEntity (5001), Schema
the wrong argument types: UndefinedEntity (5001), Schema
a function called as a procedure: NotSupported (8001), NotSupported
a timeout of 1 second: Timeout (7003), Transient
> 2. What ARX refuses first
a positional argument after a named one: InvalidArgument (1001), Validation
an OUT parameter without a type: InvalidArgument (1001), Validation
paging a procedure that returns no table: InvalidArgument (1001), Validation