SQL Server · CreateMultiple
Save an account's contacts in one call
A new customer's contacts, entered on one screen and saved in one call, in two scenarios: three contacts with different attributes, and a second save where one row fails, so none is stored.
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); MsSqlDirectHelper.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"] = "Fabrikam Manufacturing";
account["industry"] = new ArxSelectionSetValue("Industry", Industry.Manufacturing);
await service.CreateAsync(account);1 Three contacts in one CreateMultipleAsync
Three contacts in one call. Rows don't need the same attributes: ARX groups them by shape.
Already in scope: service (ArxMsSqlDbService), account (ArxEntity)
var ana = new ArxEntity("contact");
ana["account_id"] = new ArxEntityReference("account", account.RecordId);
ana["first_name"] = "Ana";
ana["last_name"] = "Silva";
ana["job_title"] = "Purchasing manager";
ana["email"] = "[email protected]";
ana["preferred_channel"] = new ArxSelectionSetValue("ContactChannel", 'E');
ana["consent_given_at"] = new DateTimeOffset(2026, 11, 2, 9, 30, 0, TimeSpan.FromHours(8));
var ben = new ArxEntity("contact");
ben["account_id"] = new ArxEntityReference("account", account.RecordId);
ben["first_name"] = "Ben";
ben["last_name"] = "Okafor";
ben["job_title"] = "Plant director";
ben["email"] = "[email protected]";
ben["preferred_channel"] = new ArxSelectionSetValue("ContactChannel", 'P');
// No email or channel for this one, but a birthday and a note.
var chen = new ArxEntity("contact");
chen["account_id"] = new ArxEntityReference("account", account.RecordId);
chen["first_name"] = "Chen";
chen["last_name"] = "Wei";
chen["job_title"] = "Accounts payable";
chen["birthday"] = new DateOnly(1985, 9, 14);
chen["notes"] = "Prefers calls before 10am.";
var ids = await service.CreateMultipleAsync(new List<ArxEntity> { ana, ben, chen });
Console.WriteLine($"Ana {ids[0]}, Ben {ids[1]}, Chen {ids[2]}");Check it in the database
Run it in any SQL tool connected to the database, after the code has run.
SELECT c.first_name, c.last_name, c.email, c.preferred_channel, c.birthday, c.notes
FROM contact c
JOIN account a ON a.id = c.account_id
WHERE a.name = 'Fabrikam Manufacturing'
ORDER BY c.id;Expect: 3 rows: Ana (E), Ben (P), Chen (a birthday and a note, no email).
2 A second save where one email is already taken: nothing is stored
A second save where one email is already taken: the whole call is refused, and no entity gets an id.
Already in scope: service (ArxMsSqlDbService), account (ArxEntity)
var dana = new ArxEntity("contact");
dana["account_id"] = new ArxEntityReference("account", account.RecordId);
dana["first_name"] = "Dana";
dana["last_name"] = "Ruiz";
dana["email"] = "[email protected]";
var anaAgain = new ArxEntity("contact");
anaAgain["account_id"] = new ArxEntityReference("account", account.RecordId);
anaAgain["first_name"] = "Ana";
anaAgain["last_name"] = "Silva";
anaAgain["email"] = "[email protected]";
try
{
await service.CreateMultipleAsync(new List<ArxEntity> { dana, anaAgain });
}
catch (ArxDatabaseException error) when (error.Code == ArxErrorCode.UniqueViolation)
{
Console.WriteLine(error.Message);
}Check it in the database
Run it in any SQL tool connected to the database, after the code has run.
SELECT count(*)
FROM contact
WHERE email = '[email protected]';Expect: 0: the whole call was refused, Dana included.
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,SampleContext.Direct.cs,MsSqlDirectHelper.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.CreateMultiple.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 RealCase1_AddContactsToAnAccount().RunAsync(new SampleContext(connectionString));
The sample, as it is in the repository.
using ARX.SDK;
using ARX.SDK.Services;
using static ArxSamples.MSSQL.SampleOutput;
namespace ArxSamples.MSSQL.Operations.CreateMultiple.RealCases;
/// <summary>
/// A new customer's contacts, entered on one screen and saved in one call, in two scenarios: three contacts with
/// different attributes, and a second save where one row fails, so none is stored.
/// </summary>
public sealed class RealCase1_AddContactsToAnAccount : ISample
{
public string Name => "create-multiple-real-1-add-contacts-to-an-account";
public string Title => "Save an account's contacts in one call";
public async Task RunAsync(SampleContext dbContext)
{
// The customer the contacts belong to.
var account = new ArxEntity("account");
account["name"] = "Fabrikam Manufacturing";
account["industry"] = new ArxSelectionSetValue("Industry", Industry.Manufacturing);
await dbContext.Service.CreateAsync(account);
await Scenario1_ThreeContactsInOneCallAsync(dbContext, account);
await Scenario2_OneEmailTaken_NothingIsStoredAsync(dbContext, account);
}
/// <summary>1. Three contacts in one call. Rows don't need the same attributes: ARX groups them by shape.</summary>
private static async Task Scenario1_ThreeContactsInOneCallAsync(SampleContext dbContext, ArxEntity account)
{
Step("1. Three contacts in one CreateMultipleAsync");
var ana = new ArxEntity("contact");
ana["account_id"] = new ArxEntityReference("account", account.RecordId);
ana["first_name"] = "Ana";
ana["last_name"] = "Silva";
ana["job_title"] = "Purchasing manager";
ana["email"] = "[email protected]";
ana["preferred_channel"] = new ArxSelectionSetValue("ContactChannel", 'E');
ana["consent_given_at"] = new DateTimeOffset(2026, 11, 2, 9, 30, 0, TimeSpan.FromHours(8));
var ben = new ArxEntity("contact");
ben["account_id"] = new ArxEntityReference("account", account.RecordId);
ben["first_name"] = "Ben";
ben["last_name"] = "Okafor";
ben["job_title"] = "Plant director";
ben["email"] = "[email protected]";
ben["preferred_channel"] = new ArxSelectionSetValue("ContactChannel", 'P');
// No email or channel for this one, but a birthday and a note.
var chen = new ArxEntity("contact");
chen["account_id"] = new ArxEntityReference("account", account.RecordId);
chen["first_name"] = "Chen";
chen["last_name"] = "Wei";
chen["job_title"] = "Accounts payable";
chen["birthday"] = new DateOnly(1985, 9, 14);
chen["notes"] = "Prefers calls before 10am.";
var ids = await dbContext.Service.CreateMultipleAsync(new List<ArxEntity> { ana, ben, chen });
Show($"Ana {ids[0]}, Ben {ids[1]}, Chen {ids[2]}");
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT c.first_name, c.last_name, c.email, c.preferred_channel, c.birthday, c.notes
* FROM contact c
* JOIN account a ON a.id = c.account_id
* WHERE a.name = 'Fabrikam Manufacturing'
* ORDER BY c.id;
* -- Expect: 3 rows: Ana (E), Ben (P), Chen (a birthday and a note, no email).
* ===========================================================
*/
}
/// <summary>2. A second save where one email is already taken: the whole call is refused, and no entity gets an id.</summary>
private static async Task Scenario2_OneEmailTaken_NothingIsStoredAsync(SampleContext dbContext, ArxEntity account)
{
Step("2. A second save where one email is already taken: nothing is stored");
var dana = new ArxEntity("contact");
dana["account_id"] = new ArxEntityReference("account", account.RecordId);
dana["first_name"] = "Dana";
dana["last_name"] = "Ruiz";
dana["email"] = "[email protected]";
var anaAgain = new ArxEntity("contact");
anaAgain["account_id"] = new ArxEntityReference("account", account.RecordId);
anaAgain["first_name"] = "Ana";
anaAgain["last_name"] = "Silva";
anaAgain["email"] = "[email protected]";
try
{
await dbContext.Service.CreateMultipleAsync(new List<ArxEntity> { dana, anaAgain });
}
catch (ArxDatabaseException error) when (error.Code == ArxErrorCode.UniqueViolation)
{
ShowError(error);
Show($"Dana has no id ({(dana.RecordId == null ? "RecordId is null" : dana.RecordId)}); contacts on the account: "
+ $"{await dbContext.Direct.CountAsync("contact", "account_id = @p0", account.RecordId!.AsInt64())}");
}
/* ===========================================================
* [DIRECT QUERY CHECK]
* -----------------------------------------------------------
* SELECT count(*)
* FROM contact
* WHERE email = '[email protected]';
* -- Expect: 0: the whole call was refused, Dana included.
* ===========================================================
*/
}
}
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. Three contacts in one CreateMultipleAsync
Ana 1, Ben 2, Chen 3
> 2. A second save where one email is already taken: nothing is stored
caught ArxDatabaseException: ARX3001 UniqueViolation: CreateMultiple on 'contact' violates unique constraint 'contact_email_key'.
Dana has no id (RecordId is null); contacts on the account: 3