Un utilisateur finance ouvre l'export de commandes de la nuit, sélectionne la colonne Amount, et la barre d'état d'Excel affiche un décompte mais aucune somme. Toutes les valeurs de la colonne sont du texte. Le code d'export appelait AsString sur chaque champ parce que c'était le moyen le plus rapide de sortir les données d'un TDataset, et le classeur a fidèlement stocké quarante mille chaînes alignées à gauche qui ressemblent seulement à des montants. Aucune exception n'est levée, le fichier s'ouvre correctement, et le rapport reste inutilisable pour l'analyse. La plupart des défauts base de données vers Excel ressemblent exactement à cela : silencieux, liés aux types, et invisibles jusqu'à ce qu'un client essaie de faire de l'arithmétique sur le résultat.
HotXLS est une bibliothèque native Object Pascal qui écrit directement des fichiers XLS et XLSX depuis Delphi et C++Builder, sans automatisation Excel. Elle offre deux routes distinctes d'un TDataset vers un classeur : le composant prêt à brancher TDataToXLS, et une boucle écrite à la main contre l'API de classeur. Ces deux routes ne sont pas interchangeables — le composant est un citoyen VCL construit sur la façade XLS — donc le bon choix dépend de l'endroit où le code s'exécute et du format attendu par le consommateur. Cet article couvre les deux, l'endroit où la route composant s'arrête, et la manière de préserver les types de champs dans chaque cas.
Les types de champs sont le vrai contrat d'export
Avant tout appel API, décidez comment chaque type de champ Delphi atterrit dans une cellule. Une cellule qui reçoit une chaîne Delphi reste une chaîne ; HotXLS ne devine pas que '1,234.50' devait être un nombre, et il ne devrait pas le faire, car le reparsing dépendant de la locale est exactement le chemin par lequel une virgule décimale allemande devient un séparateur de milliers sur un serveur anglais. Le modèle fiable consiste à affecter via les accesseurs typés : AsFloat ou AsCurrency pour les champs numériques, AsDateTime pour les dates afin que la cellule contienne un vrai numéro de série Excel plutôt qu'une chaîne formatée, et AsString seulement pour les champs réellement textuels.
La gestion de NULL mérite une décision explicite plutôt qu'une valeur par défaut. Convertir une valeur de champ avec VarToStr transforme SQL NULL en chaîne vide — donc en cellule texte — tandis que sauter l'affectation laisse la cellule vraiment vide, ce que AVERAGE, COUNT et les consommateurs de tableaux croisés attendent. Pour les colonnes monétaires, décidez avant d'écrire la boucle si NULL signifie zéro ou inconnu, car les deux deviennent visuellement identiques une fois la colonne formatée, et la différence change tous les agrégats calculés en aval.
La route composant : TDataToXLS dans les applications VCL
Pour une application VCL classique dont une requête est déjà câblée dans un module de données, TDataToXLS est la route en un appel. Il parcourt tout descendant de TDataset — FireDAC, ADO, IBX, tout ce qui implémente l'interface abstraite dataset — et produit une feuille stylée avec libellés d'en-tête, polices, bordures, sous-totaux optionnels par groupe et découpage automatique en feuilles pour les grands jeux de résultats.
var
Exporter: TDataToXLS;
begin
Exporter := TDataToXLS.Create(nil);
try
Exporter.Dataset := OrdersQuery; // any TDataset descendant
Exporter.WorksheetName := 'Orders';
Exporter.HeaderSource := hsDisplayLabel; // captions, not raw column names
Exporter.GroupFields.Add('CustomerID'); // subtotal block per customer
Exporter.RowsPerSheet := 50000; // stay below the BIFF8 row ceiling
Exporter.OnlyVisible := True; // respect Field.Visible
Exporter.SaveDatasetAs('orders.xls');
finally
Exporter.Free;
end;
end;
Deux propriétés portent l'essentiel du poids en production. HeaderSource := hsDisplayLabel écrit le DisplayLabel de chaque champ au lieu du nom brut de colonne SQL, afin que le classeur affiche « Customer Name » plutôt que CUST_NM. RowsPerSheet existe parce que le composant écrit du BIFF8, dont la grille s'arrête à 65 536 lignes par 256 colonnes ; le fixer à 50 000 découpe un grand résultat entre plusieurs feuilles avant que le plafond du format ne le tronque. L'apparence est gérée par les propriétés HeaderFont, DetailFont, GroupColor et de style de bordures, et l'ensemble DisableFormat désactive des catégories entières de formatage lorsque le consommateur veut des cellules simples. Pour tout ce qui est spécifique, les événements AfterCell et AfterRow vous remettent la plage qui vient d'être écrite pour post-traitement.
Là où le composant s'arrête
Trois contraintes sont conçues dans TDataToXLS, et les connaître à l'avance évite une refonte gênante deux sprints plus tard.
- C'est un composant VCL au sens complet. Son unité tire
Forms,ControlsetDialogs, donc le lier dans un job console ou un service Windows entraîne la VCL dans le binaire. Les unités cœur de classeur n'ont pas cette dépendance — elles n'ont besoin que deWindows,Classes,SysUtilsetVariants— c'est pourquoi le code serveur doit utiliser la boucle montrée plus bas. - Il est construit sur la façade XLS. Le composant peuple un
IXLSWorkbooket écrit du .xls (BIFF8). Aucune propriété ne le bascule en sortie OOXML. - Ses événements parlent le dialecte XLS. Le paramètre
Cell: IXLSRangedansAfterCellappartient au modèle objet XLS ; toute personnalisation par cellule écrite là est donc du code de style XLS même si le fichier est converti ensuite en .xlsx.
Produire du .xlsx depuis la sortie du composant
Lorsque le consommateur exige du .xlsx mais que la logique d'export vit déjà dans TDataToXLS, la fonction pont de l'unité lxXlsxExport convertit le classeur peuplé en un appel :
uses lxXlsxExport;
Exporter.SaveDatasetAs('orders.xls');
// the component exposes the IXLSWorkbook it populated
SaveXLSWorkbookAsXLSX(Exporter.Workbook, 'orders.xlsx');
Traitez le pont comme un transporteur de données tabulaires, pas comme un convertisseur pleine fidélité. Il copie valeurs, formules, formats numériques, couleurs de remplissage, attributs de police, largeurs de colonnes et paramètres d'affichage — et il ne copie volontairement pas les bordures, plages fusionnées, commentaires, graphiques ni formats conditionnels. Pour une grille plate avec en-tête et lignes, c'est exactement suffisant ; pour un rapport stylé, ce ne l'est pas, et la correction honnête consiste à générer directement le XLSX plutôt qu'à rapiécer le fichier converti.
La boucle écrite à la main pour services et batchs
Le code serveur doit viser directement TXLSXWorkbook. Notez la différence de durée de vie entre les deux façades avant de copier un exemple : côté XLS, TXLSWorkbook est détenu via une interface comptée par références et ne doit pas être libéré manuellement, tandis que TXLSXWorkbook est une classe ordinaire qui demande try..finally Free. Mélanger les deux conventions est un moyen fiable de fabriquer soit une fuite, soit un double free.
procedure ExportOrders(Q: TDataSet; const FileName: string);
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Row: Integer;
begin
Book := TXLSXWorkbook.Create;
try
Sheet := Book.Sheets.Add('Orders');
Sheet.Cells[1, 1].Value := 'Order No';
Sheet.Cells[1, 2].Value := 'Customer';
Sheet.Cells[1, 3].Value := 'Ordered';
Sheet.Cells[1, 4].Value := 'Amount';
Row := 2;
Q.First;
while not Q.Eof do
begin
Sheet.Cells[Row, 1].Value := Q.FieldByName('OrderNo').AsInteger;
Sheet.Cells[Row, 2].Value := Q.FieldByName('Customer').AsString;
if not Q.FieldByName('Ordered').IsNull then
Sheet.Cells[Row, 3].Value := Q.FieldByName('Ordered').AsDateTime;
Sheet.Cells[Row, 4].Value := Q.FieldByName('Amount').AsFloat;
Inc(Row);
Q.Next;
end;
Book.StreamingWrite := True; // stream sheet XML straight into the zip
Book.SaveAs(FileName);
finally
Book.Free;
end;
end;
Les lignes importantes sont les affectations typées et le garde IsNull : les dates arrivent comme numéros de série, les montants comme doubles, et les dates de commande NULL restent vraiment vides au lieu de devenir des chaînes vides. StreamingWrite := True ne change que le chemin d'enregistrement — le XML de feuille est streamé directement dans le conteneur zip au lieu d'être assemblé d'abord comme une grande chaîne — ce qui aplatit le pic mémoire au moment de SaveAs pour des nombres de lignes à six chiffres. Chaque méthode d'enregistrement possède aussi une surcharge TStream, si bien que le classeur peut aller directement dans une réponse HTTP sans toucher au disque ; l'article sur l'écriture streaming et les batchs décrit ce modèle de déploiement, et l'article sur les performances des grands classeurs couvre la suite lorsque les volumes montent encore.
Une note pratique supplémentaire : la limite de la grille XLSX est de 1 048 576 lignes par 16 384 colonnes ; la logique de découpage que RowsPerSheet fournit côté XLS est donc rarement nécessaire ici — mais un classeur d'un million de lignes est rarement ce qu'un consommateur humain veut. Quand le jeu de résultats est aussi gros, un fichier délimité est généralement le meilleur contrat ; l'article sur l'export CSV et TSV couvre les séparateurs, le comportement du BOM et la réserve de calcul de formules qui s'y applique.
Questions courantes sur l'export de datasets
TDataToXLS peut-il écrire directement du .xlsx ?
Non. Le composant est construit sur la façade XLS et écrit des fichiers BIFF8. Convertissez son classeur avec SaveXLSWorkbookAsXLSX en acceptant les limites de fidélité décrites ci-dessus, ou écrivez la boucle XLSX à la main — les projets de démonstration livrent les deux modèles côte à côte.
Le serveur doit-il avoir Microsoft Excel installé ?
Non. Les deux moteurs sont des writers natifs Object Pascal — flux d'enregistrements BIFF8 d'un côté, zip OOXML plus XML de l'autre — donc pas d'automatisation COM, pas de licence Excel sur le serveur, et pas de goulot mono-instance lorsque plusieurs exports tournent en parallèle. Chaque thread doit simplement utiliser sa propre instance de classeur, car les objets classeur ne sont pas thread-safe en partage.
Pourquoi mes nombres exportés ne se somment-ils pas dans Excel ?
Presque toujours parce qu'ils ont été écrits via AsString ou un Variant arrivé comme texte. Affectez les champs numériques avec AsFloat ou AsCurrency et laissez les formats numériques gérer la présentation ; la cellule porte alors un vrai double IEEE que tous les agrégats Excel comprennent.
Choisir un point de départ
Si l'export vit dans un outil bureau VCL et que la sortie .xls convient, commencez avec TDataToXLS et son support de groupement — c'est le moins de code. Si le code tourne sans surveillance, ou si le consommateur exige du .xlsx, écrivez la boucle. Les deux routes, avec des projets de démonstration fonctionnels pour chacune, font partie du package HotXLS Component.