Migrations EF Core sur DB2 for i

Les migrations fonctionnent avec l'outillage EF Core standard (dotnet ef migrations add, dotnet ef database update : voir la documentation Microsoft). Cette page couvre le spécifique IBM i : la création de schéma journalisée, la table d'historique, les scripts idempotents SQL PL, les limites propres à DB2 for i et le retour des valeurs IDENTITY.

Les exemples reprennent l'entité Order et le contexte OrderContext d'Introduction et configuration.


Appliquer les migrations

MigrateAsync crée le schéma (la bibliothèque) cible s'il n'existe pas, puis applique les migrations en attente :

using Microsoft.EntityFrameworkCore;

await using var db = new OrderContext();

await db.Database.MigrateAsync();   // Migrate() en synchrone

Création du schéma : journalisation automatique

Quand le schéma cible n'existe pas, le provider le crée via CREATE SCHEMA. Point important sur IBM i : un schéma créé par CREATE SCHEMA journalise automatiquement les tables qu'on y crée (contrairement à une bibliothèque créée par CRTLIB), or le contrôle de validation (commitment control) exige des tables journalisées. Les transactions EF Core fonctionnent donc immédiatement sur les tables issues des migrations, sans STRJRNPF manuel.


La table d'historique __EFMIGRATIONSHISTORY

Comme sur toute base, EF Core consigne les migrations appliquées dans une table d'historique. Fidèle à la convention d'identifiants du provider, elle est émise en majuscules dans le schéma cible : __EFMIGRATIONSHISTORY, le nom que vous verrez dans ACS. Ne la modifiez pas manuellement.


Scripts idempotents SQL PL

Pour déployer par script (DBA, CI/CD) plutôt que par Migrate(), générez un script idempotent avec la commande standard :

dotnet ef migrations script --idempotent --output migrate.sql

Le provider enveloppe chaque migration dans un bloc SQL PL composé qui consulte __EFMIGRATIONSHISTORY : le script est rejouable sans risque, chaque migration ne s'applique qu'une seule fois, quel que soit l'état de la base cible.


Limites explicites

Renommage de colonne : non supporté

DB2 for i ne supporte pas le renommage de colonne. Plutôt que d'appliquer en silence un contournement destructif, le provider fait échouer la migration avec un message actionnable. La procédure : ajouter la nouvelle colonne, copier les données, supprimer l'ancienne.

using Microsoft.EntityFrameworkCore.Migrations;

public partial class RenameCustomerColumn : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.AddColumn(
            name: "CUSTOMERNAME",
            table: "ORDERS",
            type: "VARCHAR(60)",
            nullable: false,
            defaultValue: "");

        migrationBuilder.Sql("UPDATE ORDERS SET CUSTOMERNAME = CUSTOMER");

        migrationBuilder.DropColumn(
            name: "CUSTOMER",
            table: "ORDERS");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.AddColumn(
            name: "CUSTOMER",
            table: "ORDERS",
            type: "VARCHAR(60)",
            nullable: false,
            defaultValue: "");

        migrationBuilder.Sql("UPDATE ORDERS SET CUSTOMER = CUSTOMERNAME");

        migrationBuilder.DropColumn(
            name: "CUSTOMERNAME",
            table: "ORDERS");
    }
}

La même restriction s'applique au changement de nature IDENTITY ou ROW CHANGE TIMESTAMP d'une colonne existante : la migration échoue avec un message explicite.

Une instruction par aller-retour (MaxBatchSize = 1)

SaveChangesAsync envoie une instruction par aller-retour serveur. Pour les modifications de masse, préférez ExecuteUpdateAsync / ExecuteDeleteAsync, qui s'exécutent en une seule instruction SQL côté serveur :

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

await using var db = new OrderContext();

// une seule instruction DELETE côté serveur
await db.Orders
    .Where(o => o.PlacedOn < DateTime.Today.AddYears(-2))
    .ExecuteDeleteAsync();

// une seule instruction UPDATE côté serveur
await db.Orders
    .Where(o => o.Customer == "ACME")
    .ExecuteUpdateAsync(s => s.SetProperty(o => o.Customer, "ACME SAS"));

IDENTITY : la valeur revient par FINAL TABLE

Par défaut, une clé primaire entière devient une colonne IDENTITY. À l'insertion, le provider récupère la valeur générée dans le même aller-retour, via SELECT ... FROM FINAL TABLE (INSERT ...) :

using System;
using Microsoft.EntityFrameworkCore;

await using var db = new OrderContext();

var order = new Order { Customer = "ACME", Amount = 1249.90m, PlacedOn = DateTime.Now };
db.Orders.Add(order);
await db.SaveChangesAsync();

Console.WriteLine(order.Id);   // valeur IDENTITY, renvoyée par SELECT ... FROM FINAL TABLE

Pour supprimer l'aller-retour d'identité par insertion, voyez les clés HiLo dans Mapping et types DB2 for i.


Et maintenant ?

Reconnexion au serveur...

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