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 synchroneCré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 ?
- Introduction et configuration : versions, UseNTi, options du provider
- Mapping et types DB2 for i : Unicode, zoned et packed, HiLo, ROW CHANGE TIMESTAMP
- CRUD avec EF Core 8 : exemple complet avec Entity Framework Core sur DB2 for i
- Transactions IBM i : contrôle de validation, niveaux d'isolement, savepoints