SQL Server · Execute
Handle Execute errors
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).
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 SQL Server's errors.
Already in scope: service (ArxMsSqlDbService)
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));
// A table-valued function called as a procedure. (A scalar one would run: SQL Server's EXEC accepts it and returns
// its value as the RETURN value. Call functions as functions either way.)
var function = new ArxProcedureExpression("fn_contacts_named");
function.AddParameter("Report-E601");
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 name, type
FROM sys.objects
WHERE name IN ('sp_no_such_procedure', 'sp_rename_account', 'fn_contact_count', 'sp_slow')
ORDER BY name;Expect: 3 rows: fn_contact_count FN, 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 (ArxMsSqlDbService)
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 cursor = new ArxProcedureExpression("sp_order_with_lines");
cursor.AddCursorParameter("p_lines");
await TryAsync("a cursor parameter (SQL Server has none)", () => service.ExecuteAsync(cursor));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.SQL --version 1.0.0-rc.1 - Database: a SQL Server 2022 (tested with 16.0.4225.2) database with the sample schema.
- Configuration: set
ARX_SAMPLES_SQLSERVERto 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.MSSQL;
using ArxSamples.MSSQL.Operations.Execute.RealCases;
// The connection string comes from the environment, never from the code (see "Configure the connection").
var connectionString = Environment.GetEnvironmentVariable("ARX_SAMPLES_SQLSERVER")
?? throw new InvalidOperationException("Set the ARX_SAMPLES_SQLSERVER environment variable to your SQL Server 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.MSSQL.SampleOutput;
namespace ArxSamples.MSSQL.Operations.Execute.RealCases;
/// <summary>
/// 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).
/// </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 SQL Server'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));
// A table-valued function called as a procedure. (A scalar one would run: SQL Server's EXEC accepts it and returns
// its value as the RETURN value. Call functions as functions either way.)
var function = new ArxProcedureExpression("fn_contacts_named");
function.AddParameter("Report-E601");
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 name, type
* FROM sys.objects
* WHERE name IN ('sp_no_such_procedure', 'sp_rename_account', 'fn_contact_count', 'sp_slow')
* ORDER BY name;
* -- Expect: 3 rows: fn_contact_count FN, 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 cursor = new ArxProcedureExpression("sp_order_with_lines");
cursor.AddCursorParameter("p_lines");
await TryAsync("a cursor parameter (SQL Server has none)", () => dbContext.Service.ExecuteAsync(cursor));
/* ===========================================================
* [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: DataTypeMismatch (4004), Data
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
a cursor parameter (SQL Server has none): NotSupported (8001), NotSupported