Quick Start Guide
NTi was built to solve a problem few tools tackle: connecting your IBM i to .NET and being up and running in under 15 minutes.
No driver, no native component: NTi is a 100% managed ADO.NET provider, shipped as a plain NuGet package. You write standard C# with a familiar syntax, and the API is truly asynchronous end to end: OpenAsync, execution, reading, closing, cancellation driven by the caller's token.
NTi is fully cross-platform: Windows, Linux and macOS, on x86, x64 or ARM64. On the server side, it works from V5R4 up to the latest IBM i releases.
That's it. Just code.
Prerequisites
Development side
- Visual Studio 2022, JetBrains Rider, or VS Code with the C# extension
- A compatible .NET version:
- .NET 5 / 6 / 7 / 8 / 9 / 10
- .NET Framework 4.7.2 / 4.8 / 4.8.1
- .NET Standard 2.1 (.NET Core 3.x, Mono, Xamarin)
IBM i side
- IBM i V5R4 minimum, V7R4 or later recommended
- TCP services
*DATABASE,*RMTCMDand*SIGNONstarted - An active NTi license: Start your free trial
💡 For the complete prerequisites and license installation steps, see the Installation page.
Step 1: Install NTi via NuGet
Open a terminal in your project folder and add the packages:
dotnet add package Aumerial.Data.Nti
dotnet add package Dapper
Or via the Visual Studio Package Manager Console:
Install-Package Aumerial.Data.Nti
Install-Package Dapper
💡 Why Dapper? Dapper is a lightweight micro ORM that automatically maps SQL results to your C# objects. It extends your NTi connection with methods like
Query<T>andExecute, making data access with your IBM i data clean and natural.
Step 2: Open the connection
OpenAsync is the normal path: NTi is asynchronous end to end, with no sync-over-async anywhere. The synchronous Open equivalent remains available.
using System;
using System.Data;
using Aumerial.Data.Nti;
using var conn = new NTiConnection("server=serverName;user=userName;password=password");
await conn.OpenAsync();
Console.WriteLine(conn.State == ConnectionState.Open
? "IBM i connection and NTi license OK."
: "Connection failed.");
💡 Connection pooling is inactive by default. For a web application (ASP.NET Core, Blazor) or any concurrent workload, add
pooling=trueto the connection string so physical connections are reused instead of being reopened on every request. All keywords (pooling, MFA, TLS, timeouts, default library) are described on the Connection string page.
Step 3: Read data
With Dapper, QueryAsync<MyRecord> runs the SELECT and maps each row to your class (column/property matching is case-insensitive). The synchronous Query<MyRecord> version also exists.
using System;
using System.Linq;
using Aumerial.Data.Nti;
using Dapper;
using var conn = new NTiConnection("server=serverName;user=userName;password=password");
await conn.OpenAsync();
var records = (await conn.QueryAsync("SELECT ID, LABEL FROM MYLIB.MYTABLE")).ToList();
foreach (var record in records)
Console.WriteLine($"{record.Id} : {record.Label}");
public class MyRecord
{
public string Id { get; set; }
public string Label { get; set; }
} Step 4: Write data
using System;
using Aumerial.Data.Nti;
using Dapper;
using var conn = new NTiConnection("server=serverName;user=userName;password=password");
await conn.OpenAsync();
int rows = await conn.ExecuteAsync(
"UPDATE MYLIB.MYTABLE SET LABEL = @label WHERE ID = @id",
new { label = "Updated", id = "REC-001" });
Console.WriteLine($"{rows} row(s) updated.");
💡 For a complete example using Entity Framework Core (Code First, DB First, CRUD), see the CRUD with EF Core tutorial.
Step 5: Access all IBM i resources
NTi goes far beyond SQL: CL commands with ExecuteClCommand, RPG, COBOL or CL program calls with CallProgram, exported service program procedures with CallServiceProgram, stored procedures, each with its matching asynchronous variant.
💡 Each NTi connection drives two server jobs: SQL runs in a QZDASOINIT job, commands and programs in a QZRCSRVS job. Their QTEMP and CURLIB are therefore separate.
Execute a CL command
using System;
using Aumerial.Data.Nti;
using var conn = new NTiConnection("server=serverName;user=userName;password=password");
await conn.OpenAsync();
try
{
await conn.ExecuteClCommandAsync("CRTLIB LIB(MYLIB) TEXT('My new library')");
}
catch (NTiCommandException ex)
{
// IBM i message stack: identifier and text of each message
foreach (var message in ex.Messages)
Console.WriteLine($"{message.Id} : {message.Text}");
}
💡 See the Execute a CL command tutorial.
Call an RPG program
using System;
using System.Collections.Generic;
using Aumerial.Data.Nti;
using var conn = new NTiConnection("server=serverName;user=userName;password=password");
await conn.OpenAsync();
var parms = new List
{
new NTiProgramParameter("Hello", 10).AsInput(), // CHAR(10) input
new NTiProgramParameter("", 128).AsOutput() // CHAR(128) output
};
await conn.CallProgramAsync("MYLIB", "MYPGM", parms);
string result = parms[1].GetString(0, 128);
Console.WriteLine(result);
💡 See the Call a program tutorial for typed parameters (binary, packed and zoned decimals) and variable-length receivers.
Call a service program procedure
CallServiceProgram calls an exported procedure of a service program, through the QZRUCLSP system API. Here, the C procedure gethostname fills a caller-supplied buffer. Careful: the export name is case sensitive.
using System;
using System.Collections.Generic;
using System.Data;
using Aumerial.Data.Nti;
using var conn = new NTiConnection("server=serverName;user=userName;password=password");
await conn.OpenAsync();
var buffer = new NTiProgramParameter(new byte[64], ParameterDirection.InputOutput);
var length = new NTiProgramParameter(64)
{
// BINARY(4) passed by value; by reference is the default
ServiceProgramParameterFormat = NTiServiceProgramParameterFormat.ByValue
};
var rc = await conn.CallServiceProgramAsync(
"QSYS", "QSOSRV1", "gethostname",
new List { buffer, length },
NTiServiceProgramReturnFormat.Integer);
if (rc != null && rc.GetInt() == 0)
Console.WriteLine(buffer.GetString(0, 64).TrimEnd('\0', ' '));
💡 See the Call a service program tutorial for parameter formats, the Integer and IntegerAndErrno return formats and error handling.
Call a stored procedure
using System;
using System.Data;
using System.Linq;
using Aumerial.Data.Nti;
using Dapper;
using var conn = new NTiConnection("server=serverName;user=userName;password=password");
await conn.OpenAsync();
var parameters = new DynamicParameters();
parameters.Add("myParam", dbType: DbType.Decimal, direction: ParameterDirection.Output);
var records = (await conn.QueryAsync(
"MYLIB.MYPROC",
parameters,
commandType: CommandType.StoredProcedure)).ToList();
Console.WriteLine(parameters.Get("myParam"));
💡 See the Stored procedure tutorial for a complete example using both the DataReader and Dapper approaches.
What's next?
You're up and running. Here are a few pages to go further:
- Connection string: every keyword,
pooling=true, MFA, TLS, timeouts - SQL queries:
?and@namemarkers, Dapper, multiple result sets - Call a service program: exported procedures through QZRUCLSP