Migrate from NTi 4 to NTi 5
The promise
NTi 5 is a complete rewrite of the provider: a new protocol, conversion and pooling engine, but the same public API and the same connection string keywords as NTi 4. An NTi 4 application recompiles with no code changes: migrating means updating the package reference.
dotnet add package Aumerial.Data.Nti --version 5.0.0
The deliberate differences from version 4 are few, listed below, and each has an immediate remedy. The rest of the article covers the defaults that did not change, what version 5 brings, and the versioning rule of the EF Core packages.
💡 The 4.x packages are deprecated on nuget.org, with a message pointing to 5.0.0. They remain installable for frozen applications, but fixes and new features now ship in 5.x.
Platforms and prerequisites
The package targets net472, netstandard2.1, net5.0, net8.0 and net10.0: it is consumable from .NET Framework 4.7.2, 4.8 and 4.8.1 as well as .NET 5, 6, 7, 8, 9 and 10, for every kind of workload: console, ASP.NET Core and Blazor, WinForms and WPF, Windows or systemd services, containers, Azure Functions and AWS Lambda, MAUI (via net8.0 and later).
NTi 5 is 100% managed, with no native dependency and no reliance on System.Text.Encoding.CodePages: the assembly is AnyCPU and runs on every architecture where .NET runs: x86, x64 and ARM64 (Windows, Linux, macOS), and, with .NET 8 and later, ppc64le (Linux on Power) and s390x (Linux on IBM Z).
On the server side, IBM i V5R4 minimum for the whole ADO.NET surface (SQL, CL commands, program calls). V7R4 or later is recommended; the only caveat observed on V7R2 is that some GetSchema collections rely on recent QSYS2 catalogs. The EF Core packages require IBM i 7.2 or later.
One difference, one remedy
Every behavior difference between NTi 4 and NTi 5 is deliberate and fixed in one line:
| Difference | Behavior in version 5 | Remedy |
|---|---|---|
| UDT mappings | NTiUDTMapping and SetUDTMappings are removed; distinct types are read as their base type |
Remove the mappings; convert on the application side if needed |
| Obsolete properties | UseDefaultPorts, RetreiveSuccessMessages, IgnoreNonQueryResult and PreFetch, already inert in v4, are removed |
Remove the references; fixed ports via signon port, database port and command port; prefetching via fetch ahead |
untrusted |
Defaults to false: the server TLS certificate is validated |
Deploy a trusted certificate chain; untrusted=true for tests only |
| Blocking factor | Adaptive by default: sized from the row width to target block size |
blocking factor=1000 to restore the fixed v4 sizing |
AdditionalFactorCallback |
Obsolete: still compiles, with a warning | AdditionalFactorProvider (host and user context) or AdditionalFactorAsyncProvider (cancellation token), see below |
| Sealed classes | The main classes are sealed |
Replace inheritance with composition |
| Exception hierarchy | NTiException now derives from DbException |
None: catch (NTiException) keeps working, and generic catch (DbException) handlers now also catch NTi |
| Savepoints | Missequenced Save, Rollback(name) and Release throw instead of silently doing nothing |
Fix the call order |
Double Open |
Open on an already open connection throws, per the ADO.NET contract |
Close before reopening, or create a new connection |
| Password | ConnectionString redacts the password once the connection is open |
persist security info=true for the old behavior |
Precision and Scale |
Follow the standard DbParameter contract: type byte |
Adjust int assignments (cast or literal) |
| Parameter CCSID | An NTiProgramParameter built with an explicit ccsid no longer changes the process-wide default encoding of other parameters |
Pass the ccsid on every parameter that needs it |
| Invalid values | An invalid value of a known connection string keyword throws at assignment | Fix the value; unknown keywords are still ignored, v4 compat |
The standard members involved follow the ADO.NET contract documented by Microsoft: DbException and DbParameter.Precision.
MFA: replace AdditionalFactorCallback
In v4, AdditionalFactorCallback supplied the additional authentication factor with no context. In version 5, the property still compiles (marked obsolete), but the contextual providers replace it: AdditionalFactorProvider receives the host and the user profile being authenticated, and AdditionalFactorAsyncProvider also receives the cancellation token of OpenAsync. Both may be called several times when the pool opens physical sessions.
using System;
using Aumerial.Data.Nti;
using var connection = new NTiConnection("server=MYIBMI;user=MYUSER;password=MYPASSWORD;pooling=true");
// NTi 5: the provider receives the authentication context
connection.AdditionalFactorProvider = context =>
{
Console.Write($"TOTP code for {context.User} on {context.Host}: ");
return Console.ReadLine();
};
await connection.OpenAsync();
Console.WriteLine("Connection opened with MFA.");
A static factor is still possible through the additional factor connection string keyword or the AdditionalFactor property.
The v4 defaults that did not change
Three historical defaults are deliberately preserved so that migrated applications keep their behavior:
- Pooling is off by default. Add
pooling=true, strongly recommended for web workloads.NTiConnection.ClearPool(connection)andNTiConnection.ClearAllPools()purge the pools; the client identity (application name,client accounting,client user identifier,client program identifier) is part of the pool key. - All timeouts are unlimited by default (
0= infinite): setconnect timeoutandCommandTimeoutto match your context. - Unknown connection string keywords are ignored: a v4 connection string passes through unchanged.
What version 5 brings
Without changing your code, version 5 replaces the whole engine:
- Real async end to end: from
OpenAsynctoDisposeAsync, no disguised synchronous I/O, cancellation honoring the caller's token at every phase. Make async the normal path in your services; the synchronous API remains complete. New:ExecuteClCommandAsyncandCallProgramAsync. - Service program exported procedure calls:
CallServiceProgramandCallServiceProgramAsync. default ccsid: asserts the CCSID of untagged columns (QCCSID 65535 shops).fetch ahead: the next row block is requested while the current one is being decoded.- Sovereign CCSID conversions: 179 CCSIDs (single byte, double byte and mixed SO/SI EBCDIC, Unicode, ASCII/OEM), tables extracted from an IBM i and cross-checked against JTOpen and ICU.
- An AI agent skill shipped in the package (
agents/SKILL.md); opt-in installation into your repository:dotnet msbuild -t:NTiInstallAgentSkills.
Verify the migration
An asynchronous smoke test is enough to validate the version change:
using System;
using Aumerial.Data.Nti;
using var connection = new NTiConnection("server=MYIBMI;user=MYUSER;password=MYPASSWORD;pooling=true");
await connection.OpenAsync();
using var command = connection.CreateCommand();
command.CommandText = "SELECT COUNT(*) FROM QSYS2.SYSTABLES WHERE TABLE_SCHEMA = @schema";
command.Parameters.Add(new NTiParameter { ParameterName = "@schema", Value = "QSYS2" });
var count = await command.ExecuteScalarAsync();
Console.WriteLine($"Tables cataloged in QSYS2: {count}");The EF Core side: the X.5.0 rule
The EF Core provider ships in the Aumerial.EntityFrameworkCore packages versioned 8.5.0, 9.5.0 and 10.5.0: the major version follows your EF Core version (8, 9 or 10) and the minor version 5 marks the NTi generation of the underlying engine. Pick the package aligned with your application: EF Core 8 → 8.5.0, EF Core 9 → 9.5.0, EF Core 10 → 10.5.0. Each package builds on Aumerial.Data.Nti 5, requires IBM i 7.2 or later and the sql naming convention. The details are covered by the Entity Framework Core section.
What's next?
- Connection - complete reference of the connection string keywords
- Quick Start Guide - first steps with NTi 5
- Entity Framework Core - the EF Core side of NTi