MySQL · Execute
A nightly job that commits in batches
A nightly job that commits as it goes, in two scenarios. sp_recalculate_totals recalculates order totals in batches and commits after each, so a failure late in the run keeps the batches already done, and reports what it did as a note. ARX's own call runs outside any transaction, which a procedure that commits needs. MySQL lets a procedure COMMIT inside the caller's transaction too, and that COMMIT commits everything the transaction did before it, without an error ARX could see: call such procedures outside ARX transactions and batch scopes.
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); SampleContext.Direct.cs (dbContext.Direct); MySqlDirectHelper.cs (the checks behind dbContext.Direct).
dbContext.Direct is the samples' own checking code on the plain driver, not part of ARX: leave it out of your application.
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"] = "Nightly Nets";
account["account_number"] = "ACC-E901";
var accountId = await service.CreateAsync(account);
var product = new ArxEntity("product");
product["sku"] = "SKU-E901";
product["name"] = "Recalculated product";
product["unit_price"] = 5m;
var productId = await service.CreateAsync(product);
foreach (var number in Enumerable.Range(1, 3).Select(i => $"SO-E91{i}").Concat(Enumerable.Range(1, 7).Select(i => $"SO-E92{i}")))
{
var order = new ArxEntity("sales_order");
order["order_number"] = number;
order["account_id"] = new ArxEntityReference("account", accountId);
order["ordered_on"] = new DateTime(2026, 11, 8, 1, 0, 0, DateTimeKind.Utc);
order["total"] = -1m;
var orderId = await service.CreateAsync(order);
var line = new ArxEntity("order_line");
line["order_id"] = new ArxEntityReference("sales_order", orderId);
line["product_id"] = new ArxEntityReference("product", productId);
line["quantity"] = 2;
line["unit_price"] = 5m;
await service.CreateAsync(line);
}1 Not inside an ARX transaction: the procedure's COMMIT commits the caller's work
Why not inside an ARX transaction: the transaction writes a log row, calls the job, and rolls back. The job's COMMIT has already committed the log row and its own batches, so the rollback undoes nothing.
Already in scope: service (ArxMySqlDbService), connectionString (string), accountId (ArxRecordId)
await using (var connection = new ArxMySqlConnection(connectionString))
{
await using var transaction = await connection.BeginTransactionAsync();
var log = new ArxEntity("activity_log");
log["account_id"] = new ArxEntityReference("account", accountId);
log["action"] = "nightly job E901 started";
await service.CreateAsync(log, transaction);
await service.ExecuteAsync(Job("SO-E91"), transaction);
await transaction.RollbackAsync();
}Defined once in this sample.
private static ArxProcedureExpression Job(string prefix)
{
var procedure = new ArxProcedureExpression("sp_recalculate_totals");
procedure.AddParameter("p_prefix", prefix);
procedure.AddParameter("p_batch", 3);
return procedure;
}Check it in the database
Run it in any SQL tool connected to the database, after the code has run.
SELECT (SELECT count(*) FROM activity_log WHERE action = 'nightly job E901 started') AS log_rows,
(SELECT count(*) FROM sales_order WHERE order_number LIKE 'SO-E91%' AND total = 10) AS recalculated;Expect: 1, 3: the rollback came after the procedure's COMMIT, which had committed both.
2 Run the job alone: a commit per batch
Alone: batches of three, each committed; what it did arrives as a note. A long job sets a longer timeout.
Already in scope: service (ArxMySqlDbService)
var result = await service.ExecuteAsync(Job("SO-E92"), new ArxExecuteOptions { CommandTimeout = 600 });
foreach (var message in result.Messages)
{
Console.WriteLine($"{message.Severity}: {message.Text}");
}Defined once in this sample.
private static ArxProcedureExpression Job(string prefix)
{
var procedure = new ArxProcedureExpression("sp_recalculate_totals");
procedure.AddParameter("p_prefix", prefix);
procedure.AddParameter("p_batch", 3);
return procedure;
}Check it in the database
Run it in any SQL tool connected to the database, after the code has run.
SELECT SUM(total = 10) AS recalculated, SUM(total < 0) AS still_negative
FROM sales_order
WHERE order_number LIKE 'SO-E92%';Expect: 7, 0.
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.MySQL --version 1.0.0-rc.1 - Database: a MySQL 8.4 (tested with 8.4.13) database with the sample schema.
- Configuration: set
ARX_SAMPLES_MYSQLto 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,SampleContext.Direct.cs,MySqlDirectHelper.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.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 RealCase5_ANightlyJobThatCommits().RunAsync(new SampleContext(connectionString));
The sample, as it is in the repository.
using ARX.SDK.MySQL.Services;
using ARX.SDK.Query;
using ARX.SDK.Services;
using static ArxSamples.MySQL.SampleOutput;
namespace ArxSamples.MySQL.Operations.Execute.RealCases;
/// <summary>
/// A nightly job that commits as it goes, in two scenarios. <c>sp_recalculate_totals</c> recalculates order totals in
/// batches and commits after each, so a failure late in the run keeps the batches already done, and reports what it did
/// as a note. ARX's own call runs outside any transaction, which a procedure that commits needs. MySQL lets a procedure
/// COMMIT inside the caller's transaction too, and that COMMIT commits everything the transaction did before it, without
/// an error ARX could see: call such procedures outside ARX transactions and batch scopes.
/// </summary>
public sealed class RealCase5_ANightlyJobThatCommits : ISample
{
public string Name => "execute-real-5-a-nightly-job-that-commits";
public string Title => "A nightly job that commits in batches";
public async Task RunAsync(SampleContext dbContext)
{
var account = new ArxEntity("account");
account["name"] = "Nightly Nets";
account["account_number"] = "ACC-E901";
var accountId = await dbContext.Service.CreateAsync(account);
var product = new ArxEntity("product");
product["sku"] = "SKU-E901";
product["name"] = "Recalculated product";
product["unit_price"] = 5m;
var productId = await dbContext.Service.CreateAsync(product);
// Orders whose total needs recalculating (marked -1), each with one line of two units: three for scenario 1
// (SO-E911 to SO-E913), seven for scenario 2 (SO-E921 to SO-E927).
foreach (var number in Enumerable.Range(1, 3).Select(i => $"SO-E91{i}").Concat(Enumerable.Range(1, 7).Select(i => $"SO-E92{i}")))
{
var order = new ArxEntity("sales_order");
order["order_number"] = number;
order["account_id"] = new ArxEntityReference("account", accountId);
order["ordered_on"] = new DateTime(2026, 11, 8, 1, 0, 0, DateTimeKind.Utc);
order["total"] = -1m;
var orderId = await dbContext.Service.CreateAsync(order);
var line = new ArxEntity("order_line");
line["order_id"] = new ArxEntityReference("sales_order", orderId);
line["product_id"] = new ArxEntityReference("product", productId);
line["quantity"] = 2;
line["unit_price"] = 5m;
await dbContext.Service.CreateAsync(line);
}
await Scenario1_NotInsideATransactionAsync(dbContext, accountId);
await Scenario2_RunTheJobAsync(dbContext);
}
private static ArxProcedureExpression Job(string prefix)
{
var procedure = new ArxProcedureExpression("sp_recalculate_totals");
procedure.AddParameter("p_prefix", prefix);
procedure.AddParameter("p_batch", 3);
return procedure;
}
/// <summary>
/// 1. Why not inside an ARX transaction: the transaction writes a log row, calls the job, and rolls back. The job's
/// COMMIT has already committed the log row and its own batches, so the rollback undoes nothing.
/// </summary>
private static async Task Scenario1_NotInsideATransactionAsync(SampleContext dbContext, ArxRecordId accountId)
{
Step("1. Not inside an ARX transaction: the procedure's COMMIT commits the caller's work");
await using (var connection = new ArxMySqlConnection(dbContext.ConnectionString))
{
await using var transaction = await connection.BeginTransactionAsync();
var log = new ArxEntity("activity_log");
log["account_id"] = new ArxEntityReference("account", accountId);
log["action"] = "nightly job E901 started";
await dbContext.Service.CreateAsync(log, transaction);
await dbContext.Service.ExecuteAsync(Job("SO-E91"), transaction);
await transaction.RollbackAsync();
}
Show($"rolled back, yet the log row is there: {await dbContext.Direct.CountAsync("activity_log", "action = @p0", "nightly job E901 started")}, "
+ $"and {await dbContext.Direct.CountAsync("sales_order", "order_number LIKE 'SO-E91%' AND total = 10")} orders were recalculated");
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT (SELECT count(*) FROM activity_log WHERE action = 'nightly job E901 started') AS log_rows,
* (SELECT count(*) FROM sales_order WHERE order_number LIKE 'SO-E91%' AND total = 10) AS recalculated;
* -- Expect: 1, 3: the rollback came after the procedure's COMMIT, which had committed both.
* ===========================================================
*/
}
/// <summary>2. Alone: batches of three, each committed; what it did arrives as a note. A long job sets a longer timeout.</summary>
private static async Task Scenario2_RunTheJobAsync(SampleContext dbContext)
{
Step("2. Run the job alone: a commit per batch");
var result = await dbContext.Service.ExecuteAsync(Job("SO-E92"), new ArxExecuteOptions { CommandTimeout = 600 });
foreach (var message in result.Messages)
{
Show($"{message.Severity}: {message.Text}");
}
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT SUM(total = 10) AS recalculated, SUM(total < 0) AS still_negative
* FROM sales_order
* WHERE order_number LIKE 'SO-E92%';
* -- Expect: 7, 0.
* ===========================================================
*/
}
}
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. Not inside an ARX transaction: the procedure's COMMIT commits the caller's work
rolled back, yet the log row is there: 1, and 3 orders were recalculated
> 2. Run the job alone: a commit per batch
Warning: done: 7 orders