MySQL · 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 MySQL's errors.

Already in scope: service (ArxMySqlDbService)

C#
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");
wrongTypes.AddParameter("p_reason", null);
await TryAsync("the wrong argument types", () => service.ExecuteAsync(wrongTypes));

// A function called as a procedure: MySQL looks for a procedure of that name (1305), and ARX's message says to
// set ProcedureType = ArxProcedureType.Function.
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 }));
Used above

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

SQL

Run it in any SQL tool connected to the database, after the code has run.

SELECT routine_name, routine_type
FROM information_schema.routines
WHERE routine_schema = DATABASE() AND routine_name IN ('sp_no_such_procedure', 'sp_rename_account', 'fn_contact_count', 'sp_slow')
ORDER BY routine_name;

Expect: 3 rows: fn_contact_count FUNCTION, sp_rename_account PROCEDURE, sp_slow PROCEDURE (no sp_no_such_procedure).

2 What ARX refuses first

Refused before any connection opens.

Already in scope: service (ArxMySqlDbService)

C#
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 (MySQL has none)", () => service.ExecuteAsync(cursor));
Used above

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

SQL

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

  1. .NET SDK: .NET SDK 10.0.100 or later 10.0.x (target net10.0).
  2. Create the project:
    Terminal
    dotnet new console -n ArxSample -f net10.0
    cd ArxSample
  3. Install the packages:
    Terminal
    dotnet add package ARX.SDK.MySQL --version 1.0.0-rc.1
  4. Database: a MySQL 8.4 (tested with 8.4.13) database with the sample schema.
  5. Configuration: set ARX_SAMPLES_MYSQL to its connection string. Nothing is hard-coded.
  6. Source code: add the files of the sample application this sample uses (SampleContext.cs, SampleOutput.cs, SampleContext.Application.cs, SampleCatalog.cs), then replace Program.cs and add the other file below, with the file names shown.
  7. Run it:
    Terminal
    dotnet run
  8. Expected output: below.

The files

Program.cs

Runs the sample. Generated for this page; it reads the connection string from the environment.

using ArxSamples.MySQL;
using ArxSamples.MySQL.Operations.Execute.RealCases;

// The connection string comes from the environment, never from the code (see "Configure the connection").
var connectionString = Environment.GetEnvironmentVariable("ARX_SAMPLES_MYSQL")
    ?? throw new InvalidOperationException("Set the ARX_SAMPLES_MYSQL environment variable to your MySQL connection string.");

await new RealCase7_HandleErrors().RunAsync(new SampleContext(connectionString));
RealCase7_HandleErrors.cs

The sample, as it is in the repository.

using ARX.SDK;
using ARX.SDK.Query;
using ARX.SDK.Services;
using static ArxSamples.MySQL.SampleOutput;

namespace ArxSamples.MySQL.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 MySQL'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");
        wrongTypes.AddParameter("p_reason", null);
        await TryAsync("the wrong argument types", () => dbContext.Service.ExecuteAsync(wrongTypes));

        // A function called as a procedure: MySQL looks for a procedure of that name (1305), and ARX's message says to
        // set ProcedureType = ArxProcedureType.Function.
        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 routine_name, routine_type
         * FROM information_schema.routines
         * WHERE routine_schema = DATABASE() AND routine_name IN ('sp_no_such_procedure', 'sp_rename_account', 'fn_contact_count', 'sp_slow')
         * ORDER BY routine_name;
         * -- Expect: 3 rows: fn_contact_count FUNCTION, sp_rename_account PROCEDURE, sp_slow PROCEDURE (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 (MySQL 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.

Output
  > 1. What the database refuses
      a procedure that doesn't exist: UndefinedEntity (5001), Schema
      the wrong argument types: InvalidValueFormat (4003), Data
      a function called as a procedure: UndefinedEntity (5001), Schema
      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 (MySQL has none): NotSupported (8001), NotSupported