SQL Server · Execute

Call a stored procedure

Calling a stored procedure, in three scenarios: named arguments; positional arguments; a parameter left out, so the procedure's default is used. The call is described with ArxProcedureExpression, the way a query is described with ArxQueryExpression; ARX sends EXEC with every value as a 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).

Before scenario 1 the sample runs this, so the service and the rows the scenarios use are there:

Set up
var service = new ArxMsSqlDbService(connectionString);
var account = new ArxEntity("account");
account["name"] = "Old Name Ltd";
account["account_number"] = "ACC-E101";
var accountId = await service.CreateAsync(account);

1 Named arguments

sp_rename_account(p_account_id, p_name, p_reason) with every argument named. Nothing comes back.

Already in scope: service (ArxMsSqlDbService), accountId (ArxRecordId)

C#
var procedure = new ArxProcedureExpression("sp_rename_account");
procedure.AddParameter("p_account_id", accountId.AsInt64());
procedure.AddParameter("p_name", "New Name Ltd");
procedure.AddParameter("p_reason", "rebranding");

var result = await service.ExecuteAsync(procedure);
Console.WriteLine($"renamed; outputs: {result.Outputs.Attributes.Count()}, result sets: {result.ResultSets.Count}");

Check it in the database

SQL

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

SELECT name
FROM account
WHERE account_number = 'ACC-E101';

Expect: New Name Ltd (until scenario 2 renames it again).

2 Positional arguments

The same procedure with positional arguments, in the procedure's order.

Already in scope: service (ArxMsSqlDbService), accountId (ArxRecordId)

C#
var procedure = new ArxProcedureExpression("sp_rename_account");
procedure.AddParameter(accountId.AsInt64());
procedure.AddParameter("Positional Ltd");
procedure.AddParameter("a positional call");

await service.ExecuteAsync(procedure);
Console.WriteLine("renamed to Positional Ltd");

Check it in the database

SQL

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

SELECT name
FROM account
WHERE account_number = 'ACC-E101';

Expect: Positional Ltd (until scenario 3).

3 A parameter left out: the default is used

p_reason left out: the procedure's default ('not given') is used.

Already in scope: service (ArxMsSqlDbService), accountId (ArxRecordId)

C#
var procedure = new ArxProcedureExpression("sp_rename_account");
procedure.AddParameter("p_account_id", accountId.AsInt64());
procedure.AddParameter("p_name", "Final Name Ltd");

await service.ExecuteAsync(procedure);
Console.WriteLine("renamed to Final Name Ltd, without a reason");

Check it in the database

SQL

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

SELECT name, CASE WHEN modified_on IS NOT NULL THEN 1 ELSE 0 END AS modified
FROM account
WHERE account_number = 'ACC-E101';

Expect: Final Name Ltd, 1.

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.SQL --version 1.0.0-rc.1
  4. Database: a SQL Server 2022 (tested with 16.0.4225.2) database with the sample schema.
  5. Configuration: set ARX_SAMPLES_SQLSERVER 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.MSSQL;
using ArxSamples.MSSQL.Operations.Execute.Basics;

// 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 Basic1_CallAProcedure().RunAsync(new SampleContext(connectionString));
Basic1_CallAProcedure.cs

The sample, as it is in the repository.

using ARX.SDK.SQL.Services;
using ARX.SDK.Query;
using ARX.SDK.Services;
using static ArxSamples.MSSQL.SampleOutput;

namespace ArxSamples.MSSQL.Operations.Execute.Basics;

/// <summary>
/// Calling a stored procedure, in three scenarios: named arguments; positional arguments; a parameter left out, so the
/// procedure's default is used. The call is described with <see cref="ArxProcedureExpression"/>, the way a query
/// is described with <see cref="ArxQueryExpression"/>; ARX sends <c>EXEC</c> with every value as a parameter.
/// </summary>
public sealed class Basic1_CallAProcedure : ISample
{
    public string Name => "execute-basics-1-call-a-procedure";

    public string Title => "Call a stored procedure";

    public async Task RunAsync(SampleContext dbContext)
    {
        var service = new ArxMsSqlDbService(dbContext.ConnectionString);

        var account = new ArxEntity("account");
        account["name"] = "Old Name Ltd";
        account["account_number"] = "ACC-E101";
        var accountId = await dbContext.Service.CreateAsync(account);

        await Scenario1_NamedArgumentsAsync(service, accountId);

        await Scenario2_PositionalArgumentsAsync(service, accountId);

        await Scenario3_ADefaultAsync(service, accountId);
    }

    /// <summary>1. <c>sp_rename_account(p_account_id, p_name, p_reason)</c> with every argument named. Nothing comes back.</summary>
    private static async Task Scenario1_NamedArgumentsAsync(ArxMsSqlDbService service, ArxRecordId accountId)
    {
        Step("1. Named arguments");

        var procedure = new ArxProcedureExpression("sp_rename_account");
        procedure.AddParameter("p_account_id", accountId.AsInt64());
        procedure.AddParameter("p_name", "New Name Ltd");
        procedure.AddParameter("p_reason", "rebranding");

        var result = await service.ExecuteAsync(procedure);
        Show($"renamed; outputs: {result.Outputs.Attributes.Count()}, result sets: {result.ResultSets.Count}");

        /* ===========================================================
         * [DIRECT QUERY CHECK]
         * -----------------------------------------------------------
         * SELECT name
         * FROM account
         * WHERE account_number = 'ACC-E101';
         * -- Expect: New Name Ltd (until scenario 2 renames it again).
         * ===========================================================
         */
    }

    /// <summary>2. The same procedure with positional arguments, in the procedure's order.</summary>
    private static async Task Scenario2_PositionalArgumentsAsync(ArxMsSqlDbService service, ArxRecordId accountId)
    {
        Step("2. Positional arguments");

        var procedure = new ArxProcedureExpression("sp_rename_account");
        procedure.AddParameter(accountId.AsInt64());
        procedure.AddParameter("Positional Ltd");
        procedure.AddParameter("a positional call");

        await service.ExecuteAsync(procedure);
        Show("renamed to Positional Ltd");

        /* ===========================================================
         * [DIRECT QUERY CHECK]
         * -----------------------------------------------------------
         * SELECT name
         * FROM account
         * WHERE account_number = 'ACC-E101';
         * -- Expect: Positional Ltd (until scenario 3).
         * ===========================================================
         */
    }

    /// <summary>3. <c>p_reason</c> left out: the procedure's default (<c>'not given'</c>) is used.</summary>
    private static async Task Scenario3_ADefaultAsync(ArxMsSqlDbService service, ArxRecordId accountId)
    {
        Step("3. A parameter left out: the default is used");

        var procedure = new ArxProcedureExpression("sp_rename_account");
        procedure.AddParameter("p_account_id", accountId.AsInt64());
        procedure.AddParameter("p_name", "Final Name Ltd");

        await service.ExecuteAsync(procedure);
        Show("renamed to Final Name Ltd, without a reason");

        /* ===========================================================
         * [DIRECT QUERY CHECK]
         * -----------------------------------------------------------
         * SELECT name, CASE WHEN modified_on IS NOT NULL THEN 1 ELSE 0 END AS modified
         * FROM account
         * WHERE account_number = 'ACC-E101';
         * -- Expect: Final Name Ltd, 1.
         * ===========================================================
         */
    }
}

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. Named arguments
      renamed; outputs: 1, result sets: 0
  > 2. Positional arguments
      renamed to Positional Ltd
  > 3. A parameter left out: the default is used
      renamed to Final Name Ltd, without a reason