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, *RMTCMD and *SIGNON started
  • 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> and Execute, 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=true to 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:

Reconnecting to the server...

The connection to the server was lost. The page will reload.