Le fichier était simple. La troisième ligne l’était moins.

Je viens de passer une partie de l’après-midi sur un problème qui semblait pourtant simple : lire un fichier de produits, puis envoyer les lignes dans SQL Server.

Trois champs séparés par des points-virgules, une ligne par produit, une insertion dans la base. Easy. Ma première version a bien lu les deux lignes que j’avais préparées moi-même. La troisième, venue d’un fichier plus réaliste, contenait une date impossible. Plus loin, une ligne n’avait que deux champs et un prix utilisait une virgule.

Je pensais recevoir des lignes. Le programme reçoit d’abord des octets. Il doit choisir un encodage pour les transformer en caractères, repérer les fins de ligne, reconnaître les délimiteurs, puis convertir chaque texte vers le type attendu. Une erreur à n’importe laquelle de ces étapes peut décaler tout le reste. Un caractère accentué lu avec la mauvaise page de codes ne donne pas la même panne qu’un montant invalide, même si les deux finissent par produire « import impossible » dans une première version.

Je sépare maintenant le trajet en quatre morceaux :

pour chaque ligne
    si la ligne est vide
        passer à la suivante

    découper les champs

    si le nombre de champs ou le format est mauvais
        noter le numéro de ligne et la raison du rejet
    sinon
        insérer le produit
fin

En pratique, « découper » mérite sa propre règle. Un simple Split(";") cesse de fonctionner si un nom de produit peut contenir un point-virgule entre guillemets. Le format doit dire si les guillemets sont permis, comment on représente un guillemet dans une valeur et quelle séquence termine une ligne. Si je contrôle la source, je documente un contrat minimal : encodage Windows-1252, séparateur ;, date AAAA-MM-JJ, prix avec point décimal et identifiant obligatoire.

Après le parsing vient une structure typée qui ne dépend plus du texte original :

ProduitImporte
    NoLigne        entier
    CodeProduit    texte(20)
    DateDisponible date
    Prix           décimal(10,2)

Le validateur remplit cette structure seulement lorsque chaque conversion réussit. Pour les montants, je refuse de remplacer automatiquement toutes les virgules par des points. Selon la provenance, la virgule peut séparer les décimales ou les milliers. Deviner une valeur financière est pire que rejeter clairement la ligne.

L’écriture SQL pose ensuite un choix. Une transaction pour tout le fichier donne un résultat tout ou rien, mais garde des verrous longtemps et oblige à reprendre depuis le début si la ligne 9 000 échoue. Une transaction par ligne laisse passer un fichier à moitié importé. Pour un gros fichier, je préfère de petits lots : valider d’abord, insérer par exemple 100 lignes dans une transaction, puis noter précisément le dernier lot commis.

Chaque insertion devrait être paramétrée plutôt que construite par concaténation :

INSERT INTO Produit (CodeProduit, DateDisponible, Prix)
VALUES (@CodeProduit, @DateDisponible, @Prix)

Les paramètres gardent les types et empêchent qu’une apostrophe ou du texte malveillant devienne une partie de la commande. Une contrainte unique sur CodeProduit protège aussi la base contre un doublon, même si le fichier est repris.

La reprise est le bout facile à oublier. Si l’import s’arrête après 600 lignes et que je relance le même fichier, vais-je en créer 600 autres? Je peux conserver le nom du fichier, sa taille et une empreinte, ou demander un identifiant de lot fourni par la source. Une table ImportLot enregistre son état (EnCours, Termine, Echec) et une table de rejets conserve le numéro de ligne, la valeur brute et une raison lisible. La valeur brute doit être protégée si elle contient des renseignements sensibles; un rapport d’erreurs n’est pas une excuse pour copier toutes les données partout.

Le bouton Importer n’est terminé que lorsque je peux répondre à trois questions : qu’est-ce qui a été accepté, qu’est-ce qui a été rejeté et que fera une deuxième exécution? Mon prochain import commencera donc par un contrat de fichier et un test avec des accents, des champs absents, des doublons et une coupure simulée, pas seulement par mes deux belles lignes.