SQL Server · RetrieveMultiple
Turn read errors into messages
Read errors turned into messages, in two scenarios: what the database refuses (a table or column that doesn't exist, a value of the wrong type for its column); and what ARX refuses before the database (an id of the wrong type, an operator without the values it needs, a removed column form).
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
The database refuses: each error has a code to choose the message by.
Already in scope: service (ArxMsSqlDbService)
var noTable = new ArxQueryExpression("accounts_typo");
var noColumn = new ArxQueryExpression("account");
noColumn.ColumnSet.AddColumn("fax_number");
var textForNumber = new ArxQueryExpression("account");
textForNumber.Criteria.AddCondition("employee_count", ArxConditionOperator.Equal, "forty");
foreach (var (what, query) in new[] { ("a table that doesn't exist", noTable), ("a column that doesn't exist", noColumn), ("text for a number", textForNumber) })
{
try
{
await service.RetrieveMultipleAsync(query);
}
catch (ArxDatabaseException error)
{
Console.WriteLine($"{what}: {error.Code}, the user sees \"{ForTheUser(error)}\"");
}
}Defined once in this sample.
private static string ForTheUser(ArxException error) => error.Code switch
{
ArxErrorCode.UndefinedEntity => "That list doesn't exist.",
ArxErrorCode.UndefinedAttribute => "One of the fields doesn't exist.",
ArxErrorCode.DataTypeMismatch => "One of the filter values has the wrong type.",
_ => "The search couldn't be run."
};Check it in the database
Run it in any SQL tool connected to the database, after the code has run.
SELECT CASE WHEN OBJECT_ID('dbo.accounts_typo') IS NULL THEN 1 ELSE 0 END AS no_such_table,
CASE WHEN COL_LENGTH('dbo.account', 'fax_number') IS NULL THEN 1 ELSE 0 END AS no_such_column;Expect: 1, 1: nothing was created or changed by the failed reads.
2 What ARX refuses before the database
ARX refuses before the database: these never reach it.
Already in scope: service (ArxMsSqlDbService)
var between = new ArxQueryExpression("account");
between.Criteria.AddCondition("employee_count", ArxConditionOperator.Between, 10);
var removed = new ArxQueryExpression("account");
removed.ColumnSet.AddColumn("status._opt");
var attempts = new (string What, Func<Task> Call)[]
{
("a text id for a number id", () => service.RetrieveAsync("account", "ACC-R691")),
("Between with one value", () => service.RetrieveMultipleAsync(between)),
("a removed column form", () => service.RetrieveMultipleAsync(removed)),
};
foreach (var (what, call) in attempts)
{
try
{
await call();
}
catch (ArxException error) when (error is ArxValidationException or ArxNotSupportedException)
{
Console.WriteLine($"{what}: {error.Code}");
}
}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 account_number = 'ACC-R691';Expect: 0: the refused reads were about records that don't exist, and changed nothing.
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.RetrieveMultiple.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 RealCase8_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.RetrieveMultiple.RealCases;
/// <summary>
/// Read errors turned into messages, in two scenarios: what the database refuses (a table or column that doesn't
/// exist, a value of the wrong type for its column); and what ARX refuses before the database (an id of the wrong type,
/// an operator without the values it needs, a removed column form).
/// </summary>
public sealed class RealCase8_HandleErrors : ISample
{
public string Name => "retrieve-multiple-real-8-handle-errors";
public string Title => "Turn read errors into messages";
public async Task RunAsync(SampleContext dbContext)
{
await Scenario1_WhatTheDatabaseRefusesAsync(dbContext);
await Scenario2_WhatArxRefusesFirstAsync(dbContext);
}
private static string ForTheUser(ArxException error) => error.Code switch
{
ArxErrorCode.UndefinedEntity => "That list doesn't exist.",
ArxErrorCode.UndefinedAttribute => "One of the fields doesn't exist.",
ArxErrorCode.DataTypeMismatch => "One of the filter values has the wrong type.",
_ => "The search couldn't be run."
};
/// <summary>1. The database refuses: each error has a code to choose the message by.</summary>
private static async Task Scenario1_WhatTheDatabaseRefusesAsync(SampleContext dbContext)
{
Step("1. What the database refuses");
var noTable = new ArxQueryExpression("accounts_typo");
var noColumn = new ArxQueryExpression("account");
noColumn.ColumnSet.AddColumn("fax_number");
var textForNumber = new ArxQueryExpression("account");
textForNumber.Criteria.AddCondition("employee_count", ArxConditionOperator.Equal, "forty");
foreach (var (what, query) in new[] { ("a table that doesn't exist", noTable), ("a column that doesn't exist", noColumn), ("text for a number", textForNumber) })
{
try
{
await dbContext.Service.RetrieveMultipleAsync(query);
}
catch (ArxDatabaseException error)
{
Show($"{what}: {error.Code}, the user sees \"{ForTheUser(error)}\"");
}
}
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT CASE WHEN OBJECT_ID('dbo.accounts_typo') IS NULL THEN 1 ELSE 0 END AS no_such_table,
* CASE WHEN COL_LENGTH('dbo.account', 'fax_number') IS NULL THEN 1 ELSE 0 END AS no_such_column;
* -- Expect: 1, 1: nothing was created or changed by the failed reads.
* ===========================================================
*/
}
/// <summary>2. ARX refuses before the database: these never reach it.</summary>
private static async Task Scenario2_WhatArxRefusesFirstAsync(SampleContext dbContext)
{
Step("2. What ARX refuses before the database");
var between = new ArxQueryExpression("account");
between.Criteria.AddCondition("employee_count", ArxConditionOperator.Between, 10);
var removed = new ArxQueryExpression("account");
removed.ColumnSet.AddColumn("status._opt");
var attempts = new (string What, Func<Task> Call)[]
{
("a text id for a number id", () => dbContext.Service.RetrieveAsync("account", "ACC-R691")),
("Between with one value", () => dbContext.Service.RetrieveMultipleAsync(between)),
("a removed column form", () => dbContext.Service.RetrieveMultipleAsync(removed)),
};
foreach (var (what, call) in attempts)
{
try
{
await call();
}
catch (ArxException error) when (error is ArxValidationException or ArxNotSupportedException)
{
Show($"{what}: {error.Code}");
}
}
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT count(*)
* FROM account
* WHERE account_number = 'ACC-R691';
* -- Expect: 0: the refused reads were about records that don't exist, and changed nothing.
* ===========================================================
*/
}
}
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 table that doesn't exist: UndefinedEntity, the user sees "That list doesn't exist."
a column that doesn't exist: UndefinedAttribute, the user sees "One of the fields doesn't exist."
text for a number: InvalidValueFormat, the user sees "The search couldn't be run."
> 2. What ARX refuses before the database
a text id for a number id: InvalidArgument
Between with one value: InvalidArgument
a removed column form: NotSupported