Intégrer DuckDB dans Windev : Guide pratique avec wrapper .NET C#
En bref
- Pourquoi ODBC et DLL natives ne suffisent pas pour DuckDB dans Windev.
- Développement d'un wrapper .NET C# pour encapsuler les opérations DuckDB.
- Export CSV depuis Windev, import bulk dans DuckDB, et requêtes JSON optimisées.
- Projet complet disponible sur GitHub avec code source et exemples.
Introduction
Dans un précédent article, nous avons présenté les bénéfices de DuckDB comme moteur OLAP pour analyser de gros volumes de données, en complément d’une base OLTP comme HFSQL. Mais comment, concrètement, intégrer DuckDB dans une application Windev ?
Windev permet d’appeler des bibliothèques externes via ODBC, API C/C++, ou assemblages .NET. Dans cet article, nous allons voir pourquoi l’approche par wrapper .NET C# est la plus efficace, et comment l’implémenter pas à pas.
Nous couvrirons :
- Les limitations des approches ODBC et DLL natives,
- L’architecture complète de la solution,
- Le code du wrapper .NET avec les trois méthodes clés,
- L’utilisation depuis Windev avec des exemples concrets en WLangage,
- Les performances mesurées sur des volumétries réelles,
- Les bonnes pratiques et points d’attention.
Le code complet est disponible sur GitHub pour vous permettre de démarrer rapidement.
1. Pourquoi un wrapper .NET pour DuckDB dans Windev ?
1.1 Les limitations de l’approche ODBC
DuckDB fournit bien un driver ODBC, mais son utilisation depuis Windev pose plusieurs problèmes :
- Configuration complexe : Installation du driver sur chaque poste, gestion des DSN (Data Source Name) système ou utilisateur.
- Chaînes de connexion fastidieuses : DSN-less possible mais syntaxe lourde à maintenir.
- Performances bulk insert médiocres : L’import de millions de lignes via ODBC est lent comparé à la commande native
COPY. - Gestion d’erreurs limitée : Les codes d’erreur ODBC sont génériques, difficiles à interpréter.
- Pas d’accès aux fonctionnalités avancées : Paramètres DuckDB (
PRAGMA), extensions, requêtes avecCOPYdirectement, etc.
1.2 Les limitations de l’approche DLL native
DuckDB expose une API C/C++ complète, mais l’appel depuis Windev via API Externe est laborieux :
- Gestion manuelle de la mémoire : Allocation/désallocation de buffers, marshalling de pointeurs.
- Marshalling de structures C : Les structures DuckDB (
duckdb_result,duckdb_value) sont complexes à manipuler en WLangage. - Maintenance difficile : À chaque mise à jour de DuckDB, il faut vérifier la compatibilité des signatures de fonctions.
- Code verbeux et sujet aux erreurs : Risque de fuites mémoire, erreurs de segmentation.
1.3 Pourquoi .NET/C# est la bonne approche
Un assemblage .NET C# résout tous ces problèmes :
- Client .NET officiel : Package NuGet
DuckDB.NET.Datamaintenu et documenté. - Intégration native dans Windev : qui permet d’appeler directement les méthodes C# depuis WLangage.
- Code managé : Pas de gestion manuelle de la mémoire, garbage collector automatique.
- Typage fort : Les erreurs sont détectées à la compilation, moins de bugs en production.
- Facilité de maintenance : Mise à jour du package NuGet, recompilation, c’est tout.
2. Architecture de la solution
2.1 Vue d’ensemble
┌─────────────────────┐
│ Windev Application │
└──────────┬──────────┘
│ (génère CSV)
↓
┌─────────────────────┐
│ Fichiers CSV │ articles.csv, ventes.csv, prix_achat.csv
└──────────┬──────────┘
│
↓
┌─────────────────────┐
│ Wrapper .NET C# │ ← DuckDB.NET NuGet
└──────────┬──────────┘
│
↓
┌─────────────────────┐
│ benchmark.duckdb │
└──────────┬──────────┘
│ (requêtes OLAP)
↓
┌─────────────────────┐
│ Résultats JSON │ → consommés par Windev
└─────────────────────┘
2.2 Flux de données
- Windev exporte les données HFSQL → CSV : Utilisation de
HExporteCSV()pour extraire les tables ou utilisation de fonctions d’export personnalisées. - Wrapper .NET importe CSV → DuckDB : Commande
COPYen bulk, très performante. - Windev envoie des requêtes SQL → Wrapper : Appel de méthode C# avec la requête en paramètre.
- Wrapper exécute sur DuckDB → retour JSON : Résultats sérialisés en JSON pour faciliter la désérialisation Windev.
- Windev parse JSON → affichage tableaux de bord :
JSONVersVariant()permet de consommer directement les résultats.
3. Implémentation du wrapper .NET
3.1 Configuration du projet C#
Dans Visual Studio (ou Visual Studio Code avec .NET SDK) :
Créer un projet Class Library (.NET Framework 4.8 ou .NET 6+, selon la version de Windev).
Installer le package NuGet
DuckDB.NET.Data:dotnet add package DuckDB.NET.DataAjouter une référence à
System.Text.Jsonpour la sérialisation JSON (ou Newtonsoft.Json si préféré).
3.2 Classe DuckDBWrapper - Méthodes principales
Voici les trois méthodes essentielles du wrapper :
Méthode Connect(string dbPath)
using DuckDB.NET.Data;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.Json;
public class DuckDBWrapper
{
private DuckDBConnection _connection;
/// <summary>
/// Connexion à une base DuckDB. Crée le fichier s'il n'existe pas.
/// </summary>
/// <param name="dbPath">Chemin absolu vers le fichier .duckdb</param>
/// <returns>"OK" en cas de succès, "ERREUR: message" sinon</returns>
public string Connect(string dbPath)
{
try
{
var connectionString = $"Data Source={dbPath}";
_connection = new DuckDBConnection(connectionString);
_connection.Open();
return "OK";
}
catch (Exception ex)
{
return $"ERREUR: {ex.Message}";
}
}
}
Méthode ImportCsvWithCreateTable(string createTableSql, string csvPath, string tableName)
/// <summary>
/// Crée une table et importe un fichier CSV en bulk.
/// </summary>
/// <param name="createTableSql">Script SQL de CREATE TABLE</param>
/// <param name="csvPath">Chemin absolu vers le fichier CSV</param>
/// <param name="tableName">Nom de la table à alimenter</param>
/// <returns>"OK|durée_ms" en cas de succès, "ERREUR|message" sinon</returns>
public string ImportCsvWithCreateTable(string createTableSql, string csvPath, string tableName)
{
var stopwatch = Stopwatch.StartNew();
try
{
using (var cmd = _connection.CreateCommand())
{
// Création de la table
cmd.CommandText = createTableSql;
cmd.ExecuteNonQuery();
// Import CSV avec COPY (normalisation des slashes pour DuckDB)
cmd.CommandText = $@"
COPY {tableName}
FROM '{csvPath.Replace("\\", "/")}'
WITH (HEADER TRUE, DELIMITER ';')";
cmd.ExecuteNonQuery();
}
stopwatch.Stop();
return $"OK|{stopwatch.ElapsedMilliseconds}ms";
}
catch (Exception ex)
{
stopwatch.Stop();
return $"ERREUR|{ex.Message}";
}
}
Méthode ExecuteQueryToJson(string sql)
/// <summary>
/// Exécute une requête SQL et retourne les résultats en JSON.
/// </summary>
/// <param name="sql">Requête SQL à exécuter</param>
/// <returns>JSON array en cas de succès, {"error": "message"} sinon</returns>
public string ExecuteQueryToJson(string sql)
{
try
{
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = sql;
using (var reader = cmd.ExecuteReader())
{
var results = new List<Dictionary<string, object>>();
while (reader.Read())
{
var row = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++)
{
row[reader.GetName(i)] = reader.GetValue(i);
}
results.Add(row);
}
return JsonSerializer.Serialize(results);
}
}
}
catch (Exception ex)
{
return $"{{\"error\": \"{ex.Message}\"}}";
}
}
3.3 Compilation et déploiement
- Build en Release : Compiler le projet en mode Release pour optimiser les performances.
- Copier les DLL :
DuckDBWrapper.dll(votre assemblage)DuckDB.NET.Data.dllet ses dépendances (copiées automatiquement dans le dossierbin/Release)
- Placer dans le projet Windev : Créer un sous-dossier
assemblages/dans le répertoire du projet Windev et y copier toutes les DLL.
4. Utilisation depuis Windev
4.1 Déclaration de l’assemblage
Dans le code d’initialisation du projet Windev ou de la fenêtre :
// Déclaration de l'assemblage .NET
duckdb est un DuckDbWrapper
4.2 Génération des fichiers CSV
Pourquoi écrire une fonction d’export CSV personnalisée ?
Pour contrôler le format (délimiteur, encodage, format des dates AAAA-MM-JJ) et optimiser la vitesse d’écriture.
| Table | Nombre de lignes | Export csv | HExporteCSV |
|---|---|---|---|
| Articles | 1 000 | 92 ms | 2 s |
| Ventes | 10 000 000 | 4 min 21s | 7 min 30s |
| Prix d’achat | 3 000 000 | 1 min 40s | 1 min 50s |
Procédure pour exporter les données HFSQL vers des fichiers CSV :
PROCÉDURE ExporterDonnéesVersCSV()
sRepertoireExport est une chaine = "C:\temp"
sContenu est une chaîne
fSupprime(sRepertoireExport+"articles.csv",frLectureSeule)
fSupprime(sRepertoireExport+"prix_achat.csv",frLectureSeule)
fSupprime(sRepertoireExport+"ventes.csv",frLectureSeule)
sContenu = "id_article;code_article;libelle;famille;sous_famille;is_actif"
POUR TOUT articles
sContenu += RC+ articles.id_article+";"+articles.code_article+";"+articles.libelle+";"+articles.famille+";"+articles.sous_famille+";"+articles.is_actif
FIN
fSauveTexte(sRepertoireExport+"articles.csv",sContenu)
sContenu="id_prix_achat;id_article;id_fournisseur;date_debut;date_fin;prix_achat_ht"
POUR TOUT prix_achat
sContenu += RC+prix_achat.id_prix_achat+";"+prix_achat.id_article+";"+prix_achat.id_fournisseur+";"+DateVersChaîne(prix_achat.date_debut,"AAAA-MM-JJ")+";"+DateVersChaîne(prix_achat.date_fin,"AAAA-MM-JJ")+";"+prix_achat.prix_achat_ht
FIN
fSauveTexte(sRepertoireExport+"prix_achat.csv",sContenu)
sContenu = "id_vente;date_vente;id_article;id_client;quantite;prix_vente_ht;remise_pct;id_magasin"
POUR TOUT ventes
sContenu += RC+ventes.id_vente+";"+DateVersChaîne(ventes.date_vente,"AAAA-MM-JJ")+";"+ventes.id_article+";"+ventes.id_client+";"+ventes.quantite+";"+ventes.prix_vente_ht+";"+ventes.remise_pct+";"+ventes.id_magasin
FIN
fSauveTexte(sRepertoireExport+"ventes.csv",sContenu)
Info("Export CSV terminé")
FIN
4.3 Import dans DuckDB
Procédure pour créer les tables et importer les CSV dans DuckDB :
PROCÉDURE ImporterDansDuckDB()
sCheminDB est une chaine = "C:\temp\benchmark.duckdb"
// Connexion à la base DuckDB
duckdb.Connect(sCheminDB)
// Import Articles
sSqlArticles est une chaine = [
CREATE TABLE articles (
id_article INTEGER,
code_article VARCHAR,
libelle VARCHAR,
famille VARCHAR,
sous_famille VARCHAR,
is_actif BOOLEAN
)
]
duckdb.ImportCsvWithCreateTable(sSqlArticles, "C:\temp\articles.csv", "articles")
// Import Ventes
sSqlVentes est une chaine = [
CREATE TABLE ventes (
id_vente BIGINT,
date_vente DATE,
id_article INTEGER,
id_client INTEGER,
quantite INTEGER,
prix_vente_ht DECIMAL(18,4),
remise_pct DECIMAL(5,2),
id_magasin INTEGER
)
]
duckdb.ImportCsvWithCreateTable(sSqlVentes, "C:\temp\ventes.csv", "ventes")
// Import Prix d'achat
sSqlPrixAchat est une chaine = [
CREATE TABLE prix_achat (
id_prix_achat BIGINT,
id_article INTEGER,
id_fournisseur INTEGER,
date_debut DATE,
date_fin DATE,
prix_achat_ht DECIMAL(18,4)
)
]
duckdb.ImportCsvWithCreateTable(sSqlPrixAchat, "C:\temp\prix_achat.csv", "prix_achat")
Info("Import DuckDB terminé avec succès")
FIN
4.4 Exécution de requêtes OLAP
Exemple : requête de chiffre d’affaires mensuel par famille d’articles :
PROCÉDURE RequêteCAMensuel()
jsJson est un Json
tabRésultats est un variant
// Connexion
duckdb.Connect("C:\temp\benchmark.duckdb")
// Requête CA mensuel par famille
sSql = [
SELECT
date_trunc('year', date_vente) AS annee,
date_trunc('month', date_vente) AS mois,
famille,
SUM(quantite * prix_vente_ht * (1 - remise_pct / 100.0)) AS ca_ht
FROM ventes
JOIN articles USING(id_article)
where date_vente >= '2024-01-01' AND date_vente < '2024-12-31'
GROUP BY 1, 2, 3
ORDER BY 1, 2, 3
]
jsJson = duckdb.ExecuteQueryToJson(sSql)
// Désérialisation JSON en Windev
tabRésultats = JSONVersVariant(sJson)
// Vider le champ table avant remplissage
TableSupprimeTout(TABLE_CA)
// Affichage dans un champ table
POUR CHAQUE element DE tabRésultats
TableAjouteLigne(TABLE_CA, element.annee, element.mois, element.famille, element.ca_ht)
FIN
FIN
5. Optimisations et bonnes pratiques
5.1 Gestion de la connexion
Pour éviter d’ouvrir/fermer la connexion à chaque appel :
- Singleton : Créer une instance unique du wrapper dans le projet Windev (variable globale).
- Fermeture explicite : Ajouter une méthode
Dispose()dans le wrapper C# pour libérer proprement la connexion. - Pool de connexions : Si multi-threading, envisager un pool simple.
5.2 Performance d’import CSV
Quelques astuces pour maximiser les performances :
- Utiliser
COPYplutôt queINSERT: Le bulk insert de DuckDB est extrêmement rapide. - Activer le parallélisme : Ajouter
PRAGMA threads=4;(ou plus selon le CPU) avant l’import. - Éviter les transactions explicites : Pour l’import, auto-commit est plus rapide.
Exemple d’activation du parallélisme :
public string SetThreads(int threadCount)
{
try
{
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = $"PRAGMA threads={threadCount}";
cmd.ExecuteNonQuery();
}
return "OK";
}
catch (Exception ex)
{
return $"ERREUR: {ex.Message}";
}
}
5.3 Sécurité
Quelques précautions à prendre :
- Paramétrage des requêtes : Si les requêtes contiennent des inputs utilisateur, utiliser des paramètres pour éviter les injections SQL.
- Validation des chemins fichiers : Vérifier que les chemins CSV existent et sont dans un répertoire autorisé.
- Gestion des exceptions : Toujours encapsuler les appels dans des
try/catchet retourner des messages explicites.
Exemple de requête paramétrée :
public string ExecuteQueryToJson(string sql, Dictionary<string, object> parameters)
{
try
{
using (var cmd = _connection.CreateCommand())
{
cmd.CommandText = sql;
foreach (var param in parameters)
{
var p = cmd.CreateParameter();
p.ParameterName = param.Key;
p.Value = param.Value;
cmd.Parameters.Add(p);
}
// Exécution...
}
}
catch (Exception ex)
{
return $"{{\"error\": \"{ex.Message}\"}}";
}
}
6. Cas d’usage réels testés
6.1 Volumes traités
Le tableau ci-dessous présente les volumes de données testés et les temps d’import des fichiers csv mesurés :
| Table | Nombre de lignes | Temps d’import | Remarques |
|---|---|---|---|
| Articles | 1 000 | 26 ms | Référentiel produit |
| Ventes | 10 000 000 | 1 s 433 ms | Historique 3 ans |
| Prix d’achat | 3 000 000 | 438 ms | Historique fournisseurs 3 ans |
6.2 Performances des requêtes
Mesures effectuées sur les requêtes OLAP typiques (après plusieurs exécutions à chaud) :
| Requête (sur l’année 2025) | Temps d’exécution | Résultats retournés |
|---|---|---|
| CA mensuel par famille | 177 ms | 77 lignes |
| Marge brute par article et fournisseur | 48 s | 119 986 lignes |
| Top 10 clients par chiffre d’affaires | 322 ms | 10 lignes |
Les 48 secondes pour la marge brute s’expliquent par le volume de données (10 millions de ventes jointes à 3 millions de prix d’achat) et les calculs effectués.
Cependant, c’est nettement plus rapide que ce qui serait possible avec HFSQL en OLTP. De plus, DuckDB peut être optimisé davantage avec des index ou des vues matérialisées si nécessaire.
7. Limitations et points d’attention
7.1 Limitations connues de DuckDB
- Mono-utilisateur : Le fichier
.duckdbest verrouillé pendant l’utilisation. Une seule connexion à la fois. - Pas de transactions ACID complexes : DuckDB est optimisé pour l’analytique, pas pour les transactions concurrentes.
- Optimisé lecture : Les écritures en temps réel sont possibles mais moins performantes que PostgreSQL.
7.2 Quand utiliser cette approche
✅ Bon pour :
- Tableaux de bord / rapports analytiques : Agrégations, calculs de marges, analyses temporelles.
- Analyses ad-hoc sur gros volumes : Requêtes exploratoires sans impacter la base OLTP.
- Exports de données pour BI : Préparation de cubes OLAP, datawarehouse léger.
❌ Pas pour :
- Applications multi-utilisateurs concurrentes : Préférer PostgreSQL ou SQL Server.
- Saisie transactionnelle en temps réel : HFSQL ou autre SGBD OLTP.
- Remplacer HFSQL pour l’OLTP : DuckDB ne gère pas les transactions complexes.
8. Accès au code source
Le projet complet est disponible sur demande et inclut :
- Projet Windev exemple avec procédures d’export, import et requêtes,
- Wrapper DuckDB .NET C# complet,
Conclusion
L’intégration de DuckDB dans Windev via un wrapper .NET C# apporte de nombreux bénéfices :
- Simplicité : API .NET propre et maintenable, pas de gestion mémoire manuelle.
- Performance : Gains significatifs (10-20x) sur les requêtes analytiques par rapport à HFSQL en OLTP.
- Fiabilité : Gestion d’erreurs robuste, typage fort, moins de bugs.
- Évolutivité : Facile d’ajouter de nouvelles méthodes (gestion des paramètres, nouvelles fonctionnalités DuckDB).
Cette approche permet aux développeurs Windev de bénéficier de la puissance OLAP de DuckDB sans complexité technique, avec une intégration native dans l’écosystème PC Soft.
Pour aller plus loin :
- Prochainement : “Automatiser les rafraîchissements DuckDB avec le planificateur Windev”
Si vous avez des questions ou souhaitez partager votre expérience, n’hésitez pas à ouvrir une issue sur le dépôt GitHub !