Entity Framework Core pour IBM i avec NTi

Vue d'ensemble

Aumerial.EntityFrameworkCore est le provider Entity Framework Core pour IBM i, iSeries et AS/400, construit sur le provider ADO.NET NTi : 100 % managé, sans driver ODBC, sans installation IBM i Access, sans aucune dépendance native. Il apporte LINQ, les migrations et le reverse engineering à DB2 for i, avec l'outillage EF Core standard.

Compilé AnyCPU, il s'exécute partout où .NET 8 ou supérieur s'exécute : Windows, Linux et macOS, en x64 comme en ARM64, en conteneur, et jusqu'à Linux on Power (ppc64le) et Linux on IBM Z (s390x).

Cette documentation couvre uniquement ce qui est propre à NTi et à IBM i. Pour les concepts généraux d'Entity Framework Core (DbContext, LINQ, migrations), consultez la documentation officielle Microsoft.


Versions : la règle de choix

La règle est simple : la majeure du package suit la majeure d'Entity Framework Core, la mineure suit la génération du moteur NTi (x.5 tourne sur NTi 5).

Votre application Package à installer
EF Core 8 / .NET 8 Aumerial.EntityFrameworkCore 8.5.0
EF Core 9 / .NET 9 Aumerial.EntityFrameworkCore 9.5.0
EF Core 10 / .NET 10 Aumerial.EntityFrameworkCore 10.5.0

Les trois versions partagent la même implémentation et la même surface. Le choix ne dépend donc que de votre version d'EF Core. Le package installe automatiquement sa dépendance Aumerial.Data.Nti en version 5.0.0 ou supérieure.

Côté serveur, le volet EF Core exige la version 7.2 de l'IBM i au minimum (V7R4 ou supérieure recommandée). Le provider ADO.NET seul, lui, remonte jusqu'à la V5R4.

Installation

dotnet add package Aumerial.EntityFrameworkCore

Microsoft.EntityFrameworkCore.Design est requis pour les commandes dotnet ef (migrations, scaffolding). Installez-le dans la majeure correspondant à votre version d'EF Core. Voir les outils EF Core.


Configurer le DbContext avec UseNTi

UseNTi branche EF Core sur DB2 for i. La chaîne de connexion utilise les mêmes mots-clés que le provider ADO.NET NTi. database désigne le schéma (la bibliothèque) cible. Voir Connexion pour le détail des mots-clés.

using System;
using Microsoft.EntityFrameworkCore;

public class Order
{
    public int Id { get; set; }              // colonne IDENTITY, valeur renvoyée à l'insertion
    public string Customer { get; set; } = "";
    public decimal Amount { get; set; }
    public DateTime PlacedOn { get; set; }
}

public class OrderContext : DbContext
{
    public DbSet Orders => Set();

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseNTi("server=MYIBMI;user=MYUSER;password=MYPASSWORD;database=MYLIB;");
}

EF Core s'utilise ensuite comme d'habitude, l'asynchrone étant la voie normale du provider (les équivalents synchrones existent) :

using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;

await using var db = new OrderContext();
await db.Database.MigrateAsync();   // crée le schéma (bibliothèque) si besoin, puis applique les migrations

db.Orders.Add(new Order { Customer = "ACME", Amount = 1249.90m, PlacedOn = DateTime.Now });
await db.SaveChangesAsync();        // la valeur IDENTITY revient via SELECT ... FROM FINAL TABLE

var top = await db.Orders
    .Where(o => o.Amount > 1000m)
    .OrderByDescending(o => o.Amount)
    .Take(10)
    .ToListAsync();                 // pagination FETCH FIRST / OFFSET, dialecte DB2 for i de bout en bout

Injection de dépendances

Dans un projet ASP.NET Core ou Blazor, enregistrez le contexte dans Program.cs :

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")!;
builder.Services.AddDbContext(options => options.UseNTi(connectionString));

var app = builder.Build();
app.Run();

Le contexte reçoit alors ses options par constructeur :

using Microsoft.EntityFrameworkCore;

public class OrderContext : DbContext
{
    public OrderContext(DbContextOptions options) : base(options) { }
    public DbSet Orders => Set();
}

Pour Blazor Server et les services en arrière-plan, préférez AddDbContextFactory.

Le pool de connexions est inactif par défaut. Pour une application web, ajoutez pooling=true à la chaîne de connexion.


La convention de nommage *SQL est obligatoire

Le provider qualifie les objets en SCHEMA.TABLE et exige la convention *SQL. Une chaîne de connexion qui demande naming=*SYS est rejetée dès la création du contexte, avec un message explicite. Laissez le mot-clé absent, ou indiquez naming=*SQL.

# accepté
server=MYIBMI;user=MYUSER;password=MYPASSWORD;database=MYLIB;

# rejeté à la création du contexte, avec un message explicite
server=MYIBMI;user=MYUSER;password=MYPASSWORD;database=MYLIB;naming=*SYS;

Identifiants en majuscules, entre guillemets

Le SQL émis met tous les identifiants en majuscules et les quote : "ORDERS", "CUSTOMER". Conséquence directe : les objets créés par EF Core gardent leur nom SQL comme nom système, et ce que vous voyez dans ACS correspond exactement à ce que le modèle déclare.

Dans ACS ou STRSQL, SELECT * FROM MYLIB.ORDERS fonctionne tel quel, et les tables restent accessibles depuis les outils natifs. Les mots réservés (ORDER, USER, GROUP) sont sûrs.


Options du provider

Toutes les options sont optionnelles. Les défauts fonctionnent contre n'importe quel schéma existant.

using Microsoft.EntityFrameworkCore;

public class OrderContext : DbContext
{
    public DbSet Orders => Set();

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseNTi(
            "server=MYIBMI;user=MYUSER;password=MYPASSWORD;database=MYLIB;",
            nti => nti
                .UnicodeCcsid(1208)          // CCSID des colonnes Unicode (défaut 1208 / UTF-8)
                .ForceUnicode()              // stocke toutes les colonnes texte en Unicode
                .VarcharMaxLength(8000)      // longueur par défaut des VARCHAR sans HasMaxLength
                .VarbinaryMaxLength(8000)    // idem pour VARBINARY
                .VargraphicMaxLength(8000)   // idem pour VARGRAPHIC
                .DecimalDefaults(31, 8));    // précision et échelle des decimal sans HasPrecision
}

Ces options se composent avec les annotations standard ([Unicode], HasMaxLength, HasPrecision, HasColumnType), détaillées dans Mapping et types DB2 for i.


Asynchrone de bout en bout

ToListAsync, SaveChangesAsync, MigrateAsync et consorts s'appuient sur le moteur asynchrone natif de NTi : aucun thread bloqué sur l'I/O, aucun sync-over-async, et les jetons d'annulation sont honorés à chaque phase, de l'ouverture de la connexion à la lecture des résultats.

using System;
using System.Linq;
using System.Threading;
using Microsoft.EntityFrameworkCore;

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
await using var db = new OrderContext();
var recent = await db.Orders
    .Where(o => o.PlacedOn >= DateTime.Today.AddDays(-7))
    .ToListAsync(cts.Token);

Conformément au contrat du provider NTi, une annulation effective invalide la connexion sous-jacente. L'OperationCanceledException porte le jeton de l'appelant, et la connexion est réputée cassée.


Et maintenant ?

Reconnexion au serveur...

La connexion au serveur a été perdue. La page va se recharger.