PostgreSQL · Delete
A referenced record: ARX3002, then delete in order
An account that contacts still point at can't be deleted, in two scenarios: the database refuses it (ARX3002, with the constraint's name); delete the contacts first, by their account, then the account.
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 Delete the account: refused
The account still has contacts: the foreign key refuses the delete, and names itself.
Already in scope: service (ArxPgDbService), accountId (ArxRecordId)
try
{
await service.DeleteAsync("account", accountId);
}
catch (ArxDatabaseException error) when (error.Code == ArxErrorCode.ForeignKeyViolation)
{
Console.WriteLine($"{error.Code}, constraint {error.ConstraintName}");
}Check it in the database
Run it in any SQL tool connected to the database, after the code has run.
SELECT (SELECT count(*) FROM account WHERE account_number = 'ACC-D501') AS accounts,
(SELECT count(*) FROM contact WHERE last_name = 'Kept') AS contacts;Expect: 1, 2: still there.
2 Contacts first, then the account
Delete the contacts by their account (a column match), then the account, in one batch scope: all or nothing.
Already in scope: service (ArxPgDbService), accountId (ArxRecordId)
await service.RunBatchAsync(async scope =>
{
var ofTheAccount = new ArxEntity("contact");
ofTheAccount["account_id"] = new ArxEntityReference("account", accountId);
var contacts = await scope.DeleteAsync(ofTheAccount, new ArxDeleteOptions { Match = ArxMatch.Columns("account_id") });
var account = await scope.DeleteAsync("account", accountId);
Console.WriteLine($"{contacts} contacts and {(account ? 1 : 0)} account deleted");
});Check it in the database
Run it in any SQL tool connected to the database, after the code has run.
SELECT (SELECT count(*) FROM account WHERE account_number = 'ACC-D502') AS accounts,
(SELECT count(*) FROM contact WHERE last_name = 'Closing') AS contacts;Expect: 0, 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.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.Delete.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 RealCase3_ReferencedRecord().RunAsync(new SampleContext(connectionString));
The sample, as it is in the repository.
using ARX.SDK;
using ARX.SDK.Services;
using static ArxSamples.PostgreSql.SampleOutput;
namespace ArxSamples.PostgreSql.Operations.Delete.RealCases;
/// <summary>
/// An account that contacts still point at can't be deleted, in two scenarios: the database refuses it (ARX3002, with
/// the constraint's name); delete the contacts first, by their account, then the account.
/// </summary>
public sealed class RealCase3_ReferencedRecord : ISample
{
public string Name => "delete-real-3-referenced-record";
public string Title => "A referenced record: ARX3002, then delete in order";
public async Task RunAsync(SampleContext dbContext)
{
// One customer per scenario: the first stays (its delete is refused), the second goes.
await Scenario1_TheDatabaseRefusesAsync(dbContext, await CreateCustomerAsync(dbContext, "ACC-D501", "Kept"));
await Scenario2_DeleteTheContactsFirstAsync(dbContext, await CreateCustomerAsync(dbContext, "ACC-D502", "Closing"));
}
private static async Task<ArxRecordId> CreateCustomerAsync(SampleContext dbContext, string number, string contactsLastName)
{
var account = new ArxEntity("account");
account["name"] = "Customer " + number;
account["account_number"] = number;
var accountId = await dbContext.Service.CreateAsync(account);
var contacts = new[] { "Ana", "Bo" }.Select(name =>
{
var contact = new ArxEntity("contact");
contact["first_name"] = name;
contact["last_name"] = contactsLastName;
contact["account_id"] = new ArxEntityReference("account", accountId);
return contact;
}).ToList();
await dbContext.Service.CreateMultipleAsync(contacts);
return accountId;
}
/// <summary>1. The account still has contacts: the foreign key refuses the delete, and names itself.</summary>
private static async Task Scenario1_TheDatabaseRefusesAsync(SampleContext dbContext, ArxRecordId accountId)
{
Step("1. Delete the account: refused");
try
{
await dbContext.Service.DeleteAsync("account", accountId);
}
catch (ArxDatabaseException error) when (error.Code == ArxErrorCode.ForeignKeyViolation)
{
Show($"{error.Code}, constraint {error.ConstraintName}");
}
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT (SELECT count(*) FROM account WHERE account_number = 'ACC-D501') AS accounts,
* (SELECT count(*) FROM contact WHERE last_name = 'Kept') AS contacts;
* -- Expect: 1, 2: still there.
* ===========================================================
*/
}
/// <summary>2. Delete the contacts by their account (a column match), then the account, in one batch scope: all or nothing.</summary>
private static async Task Scenario2_DeleteTheContactsFirstAsync(SampleContext dbContext, ArxRecordId accountId)
{
Step("2. Contacts first, then the account");
await dbContext.Service.RunBatchAsync(async scope =>
{
var ofTheAccount = new ArxEntity("contact");
ofTheAccount["account_id"] = new ArxEntityReference("account", accountId);
var contacts = await scope.DeleteAsync(ofTheAccount, new ArxDeleteOptions { Match = ArxMatch.Columns("account_id") });
var account = await scope.DeleteAsync("account", accountId);
Show($"{contacts} contacts and {(account ? 1 : 0)} account deleted");
});
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT (SELECT count(*) FROM account WHERE account_number = 'ACC-D502') AS accounts,
* (SELECT count(*) FROM contact WHERE last_name = 'Closing') AS contacts;
* -- Expect: 0, 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. Delete the account: refused
ForeignKeyViolation, constraint contact_account_id_fkey
> 2. Contacts first, then the account
2 contacts and 1 account deleted