Comment choisir où une application Windows stocke ses données locales — tableau de décision pour SQLite / JSON / Registre / Access

· Mis à jour le: · · SQLite, Windows, .NET, C#, Stockage de données, Registre, Access, Architecture, Tableau de décision, Conseil technique

« Un fichier INI suffit-il pour les paramètres ? » « Nos données d’historique ont grossi, on veut les faire migrer vers Access. » « Comment répartir les rôles entre le Registre et un fichier de paramètres ? » Quand on développe des applications métier Windows, le choix de l’emplacement de stockage des données est un passage obligé. Pourtant, ce choix est souvent fait un peu au hasard au début d’un projet puis jamais remis en question — jusqu’à ce que, des années plus tard, des problèmes fassent surface : « notre fichier JSON a grossi jusqu’à plusieurs dizaines de Mo et le démarrage est lent », « le fichier Access du dossier partagé se corrompt environ une fois par semaine », ou encore « nous écrivons directement sous Program Files et ça ne fonctionne plus sous Windows 11 ».

Cet article organise le stockage local des données pour les applications métier Windows en séparant « où le placer » (le choix d’un dossier) de « avec quoi le stocker » (le choix d’un format ou d’un moteur). En reprenant le format de tableau de décision déjà utilisé plusieurs fois sur ce blog, nous résumons les points forts et les pièges de SQLite, JSON, du Registre et d’Access.

1. L’essentiel d’abord

  • Choisir où stocker les données revient en réalité à deux décisions indépendantes : « où le placer » et « avec quoi le stocker ». Se tromper sur la première entraîne des incidents de permissions et de multi-utilisateurs ; se tromper sur la seconde entraîne des incidents de corruption, de performance et de maintenance.
  • La règle de base pour le placement : utilisez %LOCALAPPDATA% (Environment.SpecialFolder.LocalApplicationData) pour les paramètres et données propres à chaque utilisateur, %PROGRAMDATA% pour les données partagées entre tous les utilisateurs, et n’écrivez jamais dans le même dossier que l’exécutable (sous Program Files).1
  • Il n’existe que deux formats de premier choix, tout simplement. Utilisez un fichier JSON pour des paramètres petits et structurés, et SQLite pour des données métier, un historique ou toute donnée croissante que vous devez rechercher. Ces deux options couvrent l’immense majorité des besoins de stockage local des applications métier.2
  • Le Registre est « un endroit pour de petits indicateurs et des informations d’intégration avec Windows lui-même » — ce n’est pas le magasin de données de votre application. L’utiliser sans comprendre la redirection de Registre 32 bits/64 bits (Wow6432Node) vous conduira à des problèmes du type « la valeur que j’ai écrite n’est pas là ».3
  • Il ne reste presque plus aucune raison de choisir Access (.accdb) comme magasin de données pour un nouveau projet. Même utilisé pour l’intégration avec des actifs existants, il s’accompagne de la contrainte de déploiement que le nombre de bits du fournisseur ACE doit correspondre à celui de l’application.4
  • Quel que soit le format, les informations confidentielles (mots de passe, clés API) nécessitent toujours un traitement à part. Ne les stockez pas en clair dans du JSON ou dans le Registre — protégez-les avec DPAPI. Pour plus de détails, voir l’article séparé « Stockage des informations confidentielles dans une application Windows - éviter les configurations en clair grâce à DPAPI ».

2. Classer les données à stocker en quatre catégories

Avant de décider avec quoi stocker vos données, classez la nature des données que vous vous apprêtez à stocker. Les données locales des applications métier se répartissent généralement en quatre catégories.

Catégorie Exemples Caractéristiques
Paramètres Cible de connexion, disposition de l’écran, dernier dossier ouvert Petit volume. Lu intégralement au démarrage. L’utilisateur souhaite parfois l’éditer directement
Données métier / historique Résultats de mesure, historique de traitement, copie locale de données de référence Croît en continu. Besoin de recherche/agrégation. Une corruption a un fort impact métier
Cache Miniatures, ressources téléchargées Régénérable en cas de perte. Nécessite une gestion de l’espace
Informations confidentielles Mots de passe enregistrés, jetons Faible volume. Ne doit jamais être stocké en clair

L’argument central de cet article est que l’emplacement et le format adaptés diffèrent pour chacune de ces catégories. Si votre application est du genre où « les paramètres et l’historique sont tous entassés dans un seul fichier XML », refaire cette classification est le premier pas vers l’amélioration.

3. Où le placer — les bases du choix du dossier

En .NET, basez votre choix sur les emplacements obtenus via Environment.GetFolderPath.5

Emplacement Comment l’obtenir Usage
%LOCALAPPDATA%\NomSociété\NomApplication SpecialFolder.LocalApplicationData Emplacement par défaut pour les données propres à l’utilisateur. À utiliser en premier
%APPDATA%\NomSociété\NomApplication (Roaming) SpecialFolder.ApplicationData Uniquement pour les paramètres que l’on souhaite faire suivre l’utilisateur dans un environnement à profils itinérants
%PROGRAMDATA%\NomSociété\NomApplication SpecialFolder.CommonApplicationData Données partagées entre tous les utilisateurs. Nécessite une conception de la LCA
Sous Documents SpecialFolder.MyDocuments Uniquement pour les livrables que l’utilisateur considère comme ses propres fichiers (rapports exportés, etc.)

En code, cela se résume à peu de chose, mais encapsuler l’imbrication « NomSociété\NomApplication » et la création du dossier au premier lancement dans un utilitaire commun évite que les emplacements de stockage ne se multiplient de façon anarchique.

public static class AppPaths
{
    public static string DataDir { get; } = CreateDir(
        Environment.SpecialFolder.LocalApplicationData);

    private static string CreateDir(Environment.SpecialFolder root)
    {
        var dir = Path.Combine(
            Environment.GetFolderPath(root), "KomuraSoft", "MyApp");
        Directory.CreateDirectory(dir);  // Ne fait rien si le dossier existe déjà
        return dir;
    }
}

La raison d’utiliser Environment.GetFolderPath plutôt que de construire la variable d’environnement %LOCALAPPDATA% par concaténation de chaînes est qu’il retourne le bon emplacement même sous un compte de service, un autre utilisateur, ou dans un environnement où une redirection de dossier est configurée. Cela évite aussi l’incident où le chemin change au moment où l’application s’exécute sous un autre compte via le Planificateur de tâches (une variante du problème « ça marche en manuel » décrit à la section 5 de l’article sur le Planificateur de tâches).

Voici trois pièges à connaître.

  • N’écrivez pas dans le dossier contenant l’exécutable. Les utilisateurs standard ne peuvent pas écrire sous Program Files. Sur les anciennes applications 32 bits, la couche de compatibilité de l’UAC peut rediriger silencieusement les écritures vers VirtualStore, provoquant le symptôme déroutant où « le contenu du fichier de paramètres diffère selon que l’application a été exécutée en tant qu’administrateur ou en tant qu’utilisateur standard ».
  • ProgramData est « accessible en écriture mais pas sûr ». Avec la LCA par défaut, les fichiers créés par un utilisateur peuvent ne pas être modifiables par un autre. Si vous avez besoin d’un accès en lecture/écriture partagé pour tous les utilisateurs, faites créer le dossier par l’installateur et configurez explicitement la LCA.
  • Ne faites pas de Roaming votre défaut. Dans les environnements de domaine à profils utilisateur itinérants, tout ce qui se trouve sous Roaming est synchronisé à l’ouverture et à la fermeture de session. Placer des données volumineuses ou spécifiques à la machine (caches, paramètres matériels) dans Roaming provoque des retards de synchronisation ou une « contamination » d’autres machines. En cas de doute, utilisez Local.

4. Avec quoi le stocker — profil des quatre options

4.1 Fichiers JSON — le premier choix pour les paramètres

JSON se lit et s’écrit simplement avec System.Text.Json, est lisible par un humain, et se prête bien à la gestion Git et à la comparaison de différences — autant d’avantages qui conviennent parfaitement à l’usage « paramètres ». Deux points de vigilance.

Prémunissez-vous contre la corruption. Si l’alimentation est coupée en cours d’écriture, un fichier à moitié écrit peut rester en place, le rendant illisible au prochain démarrage. La technique standard consiste à « écrire dans un fichier temporaire, puis remplacer » — en .NET, File.Replace fournit une opération de remplacement avec sauvegarde.

var json = JsonSerializer.Serialize(settings, options);
var tmp = path + ".tmp";
File.WriteAllText(tmp, json);
if (File.Exists(path))
    File.Replace(tmp, path, path + ".bak");
else
    File.Move(tmp, path);

Si vous intégrez dès le départ un comportement dégradé côté lecture — « si le fichier est corrompu, essayer le .bak, et si cela échoue aussi, démarrer avec les valeurs par défaut et avertir l’utilisateur » — la corruption du fichier de paramètres cesse de générer des tickets de support.

N’en faites pas un magasin de données. Le domaine d’application de JSON correspond aux tailles où « tout lire au démarrage, tout écrire à la fermeture » reste valable (à titre indicatif, jusqu’à quelques centaines de Ko). Dès que vous commencez à y mettre un historique qui s’accumule en continu ou des données nécessitant une recherche par enregistrement, c’est le signal qu’il faut passer à SQLite.

4.2 SQLite — le premier choix pour les données croissantes et consultables

SQLite est un moteur de base de données embarqué sans serveur, en fichier unique, du domaine public. Depuis .NET, vous pouvez utiliser Microsoft.Data.Sqlite, le fournisseur ADO.NET maintenu par Microsoft, ou le fournisseur SQLite d’EF Core.2 Microsoft lui-même recommande SQLite comme moyen de stocker les données locales des applications Windows,6 et il est légitime de se tourner d’abord vers SQLite pour « des données structurées qui croissent localement ».

Montrons d’abord en code à quel point c’est léger. Ajoutez Microsoft.Data.Sqlite via NuGet, et vous pouvez démarrer en spécifiant simplement un chemin de fichier — aucune configuration de serveur, aucun écran de gestion de chaîne de connexion n’est nécessaire.

using Microsoft.Data.Sqlite;

var dbPath = Path.Combine(AppPaths.DataDir, "app.db");
using var conn = new SqliteConnection($"Data Source={dbPath}");
conn.Open();

// Uniquement au premier lancement : activation du mode WAL et création des tables
using (var cmd = conn.CreateCommand())
{
    cmd.CommandText = """
        PRAGMA journal_mode=WAL;
        CREATE TABLE IF NOT EXISTS measurement (
            id         INTEGER PRIMARY KEY AUTOINCREMENT,
            device_id  TEXT    NOT NULL,
            value      REAL    NOT NULL,
            created_at TEXT    NOT NULL DEFAULT (datetime('now'))
        );
        CREATE INDEX IF NOT EXISTS ix_measurement_device
            ON measurement(device_id, created_at);
        """;
    cmd.ExecuteNonQuery();
}

// Toujours utiliser des paramètres pour les insertions (ne jamais construire le SQL par concaténation de chaînes)
using (var cmd = conn.CreateCommand())
{
    cmd.CommandText =
        "INSERT INTO measurement (device_id, value) VALUES ($device, $value)";
    cmd.Parameters.AddWithValue("$device", "CAM-01");
    cmd.Parameters.AddWithValue("$value", 23.5);
    cmd.ExecuteNonQuery();
}

Comme vous pouvez le voir, pour un effort à peu près équivalent à « ajouter des lignes à un fichier JSON », vous obtenez une recherche indexée, de l’agrégation et un historique de taille illimitée. Si vous souhaitez une couche ORM, le fournisseur SQLite d’EF Core se pose au-dessus de cette bibliothèque.

Cela posé, voici les points pratiques essentiels.

  • Activez le mode WAL. C’est le PRAGMA journal_mode=WAL; dans le code ci-dessus. Il améliore la concurrence entre lecture et écriture, si bien qu’une conception où le thread d’interface et un traitement en arrière-plan touchent tous deux la même base de données a moins de risques de se bloquer. Le réglage WAL est persisté dans le fichier de base de données lui-même, il n’est donc pas nécessaire de l’émettre à chaque connexion.
  • Concentrez les écritures sur un seul chemin par processus. Les écritures SQLite sont exclusives au niveau de la base de données. Si plusieurs threads doivent écrire, une conception qui fait passer les écritures par une file d’attente unique est plus sûre. De plus, lorsque vous effectuez un grand nombre de petits INSERT, les regrouper dans une transaction explicite les rend nettement plus rapides qu’un commit ligne par ligne.
  • Ne le placez pas sur un partage réseau. Le verrouillage de fichiers via SMB est sujet à des problèmes dépendant de l’environnement, et le projet SQLite lui-même cite le partage sur un système de fichiers réseau comme cause première de corruption.7 Dès que vous avez besoin d’un accès simultané depuis plusieurs machines ou utilisateurs, vous êtes passé dans le domaine des bases de données client-serveur (SQL Server Express, etc.).
  • Sachez qu’il n’existe que quatre types. Sous le capot, SQLite dispose de INTEGER / REAL / TEXT / BLOB, et les dates ainsi que les GUID sont stockés en TEXT. Vérifier une fois les conventions de mappage de types de Microsoft.Data.Sqlite vous évitera bien des maux de tête lors de la comparaison et du tri de dates.8 Si l’exemple ci-dessus utilise datetime('now') (UTC) pour created_at, c’est parce que mélanger l’heure locale cause des problèmes de tri et de passage à l’heure d’été. Convertir vers l’heure locale uniquement à l’affichage est la politique la plus sûre.
  • Sauvegardez avec VACUUM INTO ou l’API Backup, pas avec une simple copie de fichier. Une copie naïve d’un fichier de base de données actif peut capturer des incohérences entre le WAL et le fichier principal (détails à la section 6).

4.3 Registre — uniquement pour les petits indicateurs et les informations d’intégration Windows

Le Registre convient aux informations qui s’intègrent avec Windows lui-même — « est-ce installé », « enregistrement au démarrage », « associations de fichiers » — et à de très petits paramètres utilisateur, rien de plus. La règle de base : les paramètres que l’application utilise pour elle-même vont sous HKCU, et les informations communes à la machine sont écrites dans HKLM par l’installateur (évitez une conception qui écrit dans HKLM au moment de l’exécution, car cela exigerait des privilèges d’administrateur).

Le plus grand piège est le nombre de bits. Sous Windows 64 bits, HKLM\Software tel que vu par un processus 32 bits est redirigé vers HKLM\Software\Wow6432Node.3 Les symptômes tels que « la valeur est présente dans l’Éditeur du Registre mais l’application ne peut pas la lire » ou « une valeur écrite par l’application 32 bits n’est pas visible depuis l’outil de maintenance 64 bits » relèvent presque toujours de ce phénomène. Il tend à apparaître lors d’une migration vers AnyCPU ou d’un passage au 64 bits, à garder donc à l’esprit dans le même contexte que les problèmes 32 bits/64 bits qui touchent COM et ActiveX (voir « Pièges de COM/OCX/ActiveX - nombre de bits de Visual Studio et droits d’administrateur »).

Si vous devez absolument lire la vue de l’autre nombre de bits depuis .NET (par exemple, une application encore maintenue en 32 bits qui doit lire une valeur enregistrée côté 64 bits), vous pouvez spécifier explicitement la vue avec RegistryView.

using Microsoft.Win32;

// Lire la vue 64 bits de HKLM depuis un processus 32 bits
using var hklm64 = RegistryKey.OpenBaseKey(
    RegistryHive.LocalMachine, RegistryView.Registry64);
using var key = hklm64.OpenSubKey(@"SOFTWARE\KomuraSoft\MyApp");
var installDir = key?.GetValue("InstallDir") as string;

Inversement, le moment où cette spécification devient nécessaire est aussi le signe que vous avez repoussé une décision de conception : « quel nombre de bits fait foi pour l’écriture ». La bonne approche consiste à aligner le nombre de bits de celui qui écrit et de celui qui lit.

Placer dans le Registre des données de plus de quelques Ko, ou de nature tabulaire, est désavantageux sous tous les angles — sauvegarde, migration, diagnostic. Laissez cet usage aux fichiers (JSON / SQLite).

4.4 Access (.accdb) — presque jamais justifié pour un nouveau projet, à accepter tel quel pour l’intégration avec l’existant

Autrefois, quand on parlait de base de données locale pour les applications métier, on pensait à Access (JET/ACE), mais aujourd’hui il n’y a presque plus aucune raison de le choisir pour un nouveau développement. La raison tient principalement à la distribution. Accéder à un .accdb depuis du code nécessite le fournisseur ACE (Access Database Engine), et la connexion échoue si le nombre de bits de l’application ne correspond pas à celui d’ACE.4 Il existe aussi des problèmes de coexistence avec le nombre de bits d’Office, et « ça marche sur la machine de développement mais échoue chez le client avec Le fournisseur Microsoft.ACE.OLEDB.12.0 n'est pas enregistré sur l'ordinateur local » est un cas de support classique. La nécessité d’installer un package redistribuable (Access Database Engine 2016 Redistributable) alourdit également ce qu’il faut livrer.9

Malgré cela, il existe des situations bien réelles où Access est impliqué de façon incontournable : intégration de données avec un système métier Access existant, lecture de données de référence constituées dans Access, etc. Dans ces cas, nous recommandons les compromis suivants :

  • fixez le nombre de bits du processus qui lit et écrit (x86 fixe est souvent le choix le plus réaliste), et faites vérifier par l’installateur la présence de l’ACE correspondant
  • évitez, comme choix de conception, les écritures concurrentes de plusieurs personnes sur un .accdb situé dans un dossier partagé (le coût de récupération en cas de casse n’en vaut pas la peine)
  • gardez à long terme une trajectoire de migration vers SQLite ou une base de données serveur

Le traitement des actifs existants, y compris les actifs Excel/VBA, est également couvert dans « Qu’est-ce que VBA - contraintes, avenir et quand le remplacer ».

5. Tableau de décision

Aspect Fichier JSON SQLite Registre Access (.accdb)
Données adaptées Petits paramètres Données structurées croissantes, recherche/agrégation Petits indicateurs, intégration Windows Intégration avec des actifs Access existants
Volume de données indicatif Jusqu’à quelques centaines de Ko Jusqu’à quelques dizaines de Go Jusqu’à quelques Ko Jusqu’à 2 Go (limite des spécifications)
Recherche/agrégation ✕ (suppose une lecture intégrale) ◎ (SQL) ○ (SQL)
Lisible directement par un humain △ (outil nécessaire) △ (Access nécessaire)
Résistance à la corruption △ (mesures à implémenter soi-même) ○ (transactions)
Accès concurrent multi-processus ○ (au sein d’une même machine)
Partage entre plusieurs machines ✕ (en pratique)
Éléments de déploiement supplémentaires Aucun Aucun (inclus via NuGet) Aucun Fournisseur ACE requis

Comme le montre la dernière ligne, aucune de ces technologies de stockage local n’est adaptée au « partage entre plusieurs machines ». Les placer dans un dossier partagé peut donner l’impression de permettre le partage, mais JSON n’a aucun mécanisme d’exclusion, le verrouillage de SQLite via SMB n’est pas fiable, et Access atteint ses limites en même temps que le risque de corruption. Dès qu’apparaît un besoin où plusieurs sites ou plusieurs utilisateurs touchent les mêmes données, considérez que c’est la ligne à partir de laquelle il faut mettre en place une base de données serveur telle que SQL Server Express, ou une API Web.

6. Résistant à la corruption, migrable, restaurable — conception commune indépendante du format

Quel que soit le format choisi, il y a trois éléments de conception dont vous aurez inévitablement besoin si vous exploitez l’application pendant plusieurs années. Les inclure ou non dans la première version fait une grande différence sur le coût de maintenance ultérieur.

6.1 Versionner le schéma / format

Quand vous mettez à jour l’application, la forme des données que vous stockez change aussi. Le moment où « une nouvelle version lit des données écrites par une ancienne version » finit toujours par arriver, donnez donc aux données elles-mêmes une version de format.

Pour SQLite, PRAGMA user_version existe précisément dans ce but.

int GetVersion(SqliteConnection conn)
{
    using var cmd = conn.CreateCommand();
    cmd.CommandText = "PRAGMA user_version";
    return Convert.ToInt32(cmd.ExecuteScalar());
}

void Migrate(SqliteConnection conn)
{
    void Exec(string sql)
    {
        using var cmd = conn.CreateCommand();
        cmd.CommandText = sql;
        cmd.ExecuteNonQuery();
    }

    var v = GetVersion(conn);
    if (v > 2)
        // Cas où une ancienne version de l'application ouvre une base créée par une version plus récente.
        // Il est plus sûr de s'arrêter ici que de toucher à un schéma inconnu
        throw new InvalidOperationException(
            $"Cette base de données (version {v}) a été créée par une version plus récente de l'application.");

    using var tx = conn.BeginTransaction();
    if (v < 1) Exec("ALTER TABLE measurement ADD COLUMN unit TEXT");
    if (v < 2) Exec("CREATE TABLE operator (id INTEGER PRIMARY KEY, name TEXT)");
    Exec("PRAGMA user_version = 2");
    tx.Commit();
}

C’est la forme minimale de ce qu’on appelle généralement une migration : vérifier la version au démarrage et n’appliquer que la différence. Si le code rejette d’emblée « une version plus récente que lui-même », c’est pour éviter l’accident où, si l’application est ramenée à une version antérieure (rollback), l’ancien code écrit dans un schéma qu’il ne comprend pas et le corrompt. Le même principe s’applique à JSON : donnez à la racine un champ "version": 2, insérez une étape de conversion depuis les anciens formats à la lecture, et refusez de charger des formats trop récents. « Ne jamais publier un format de données sans numéro de version » — respectez cette seule règle, et votre futur vous en sera reconnaissant.

6.2 Décider du comportement dégradé en cas de corruption

La section 4 a abordé les mesures anti-corruption propres à chaque format (écritures atomiques pour JSON, transactions pour SQLite), mais malgré cela, vous rencontrerez tout de même des « données illisibles » : panne de disque, mise en quarantaine par un faux positif d’antivirus, édition manuelle par un utilisateur. Si vous n’avez pas décidé comment l’application doit se comporter dans cette situation, vous vous retrouvez avec une application qui ne démarre même pas.

  • Si les paramètres ne peuvent pas être lus → démarrer avec les valeurs par défaut et en informer l’utilisateur (revenir silencieusement aux valeurs par défaut se transforme en demandes de support du type « mes paramètres ont disparu »).
  • Si des données métier ne peuvent pas être lues → présenter, via un mode lecture seule ou un écran d’erreur, exactement quel fichier est corrompu. Ne pas réparer automatiquement en écrasant (cela détruit les preuves).
  • Si une sauvegarde existe → proposer de la restaurer. Cependant, la restauration automatique porte en elle le risque inverse « un faux positif de corruption qui fait revenir à d’anciennes données », donc en règle générale, faites intervenir une action utilisateur.

6.3 Les sauvegardes : « peut-on restaurer » compte plus que « en a-t-on une »

Contrairement à une base de données serveur, personne ne sauvegarde les données locales à votre place. Si l’application doit s’en charger, décidez de ces trois points.

  • Quoi : les données métier en font partie, le cache en est exclu ; pour les informations confidentielles, tenez compte du fait que, de par la nature de DPAPI, elles ne peuvent être déchiffrées que par le même utilisateur sur la même machine (une procédure de migration séparée est nécessaire pour un changement de machine).
  • Quand/où : au démarrage ou quotidiennement, dans un dossier backup versionné sous %LOCALAPPDATA%. Le fait de le placer aussi sur un dossier partagé ou un dossier déjà couvert par une sauvegarde de PC existante relève d’une discussion avec les équipes d’exploitation.
  • Comment : pour SQLite, jamais de simple copie de fichier pendant qu’il est actif. VACUUM INTO 'backup.db' prend un instantané cohérent en une seule instruction.
// VACUUM INTO ne crée pas le dossier parent et échoue si la destination existe déjà.
// Créer le dossier et déterminer un nom de fichier non conflictuel au préalable
var backupDir = Path.Combine(AppPaths.DataDir, "backup");
Directory.CreateDirectory(backupDir);
var backupPath = Path.Combine(backupDir, $"app-{DateTime.Now:yyyyMMdd-HHmmss}.db");

using var cmd = conn.CreateCommand();
cmd.CommandText = "VACUUM INTO $path";
cmd.Parameters.AddWithValue("$path", backupPath);
cmd.ExecuteNonQuery();

Comme les générations continuent de s’accumuler, ajoutez aussi, après chaque sauvegarde, une étape qui « ne conserve que les N générations les plus récentes et supprime les plus anciennes ».

Et faites au moins une fois une répétition de la restauration. C’est une situation classique des systèmes métier : des fichiers de sauvegarde existent, mais personne ne sait comment les restaurer, ou personne ne l’a jamais essayé. Si vous rédigez une procédure de transfert des données vers une nouvelle machine au moment du remplacement d’un PC, vous trouverez généralement des failles dans votre conception de sauvegarde (des identifiants protégés par DPAPI qui ne se transfèrent pas, des chemins contenant le nom d’utilisateur qui se brisent sous un autre utilisateur, etc.). Pour la façon d’effacer les données au moment de mettre au rebut un PC, voir aussi « Liste de contrôle avant la mise au rebut d’un PC Windows ».

7. Repères pour les cas fréquents d’hésitation

  • « C’est un paramètre, mais ça risque de grossir » — S’il y a une chance que l’usage « tout lire au démarrage » ne tienne plus, partez directement sur SQLite. Créer une « table settings » dans SQLite n’a rien de problématique.
  • « Migration depuis INI/XML » — S’il s’agit d’un simple remplacement de format, allez vers JSON ; si des données de type historique s’y sont mêlées à ce moment-là, séparez-les et allez vers SQLite. Conserver côté lecture un repli vers l’ancien format pendant une ou deux versions rend la migration plus sûre.
  • « On nous demande de pouvoir consulter dans Excel » — Plutôt que de faire d’Excel/Access le magasin de données, stocker les données dans SQLite et ajouter une fonction d’export vers CSV/Excel satisfait à la fois la fiabilité des données et la demande. Pour la façon de construire une sortie de rapport, voir « Comment créer une sortie de rapport Excel ».
  • « Plusieurs processus veulent lire et écrire dans le même fichier » — Au sein d’une même machine, SQLite (WAL) peut absorber une bonne partie de la charge, mais vous devez tout de même concevoir la gestion des conflits d’écriture. Si vous vous coordonnez au niveau du fichier, utilisez les modèles d’exclusion de « Bonnes pratiques d’intégration de fichiers et de verrouillage ».
  • « On veut partager entre plusieurs machines » — C’est là que l’on quitte le stockage local. Le premier choix est une architecture client-serveur avec SQL Server Express (gratuit, jusqu’à 10 Go par base de données) installé sur une machine équivalente à un serveur de fichiers. Notez que SQL Server « LocalDB », malgré son nom, est un environnement mono-utilisateur destiné au développement, à ne donc pas choisir à des fins de partage. Dès que l’on franchit les sites ou que l’accès est nécessaire depuis l’extérieur de l’entreprise, c’est la ligne à partir de laquelle envisager une conception passant par une API Web.

8. Résumé

Le choix de l’emplacement de stockage se décide sans trop d’hésitation dans la plupart des cas en le séparant en « où le placer » (LocalAppData / ProgramData, et jamais Program Files) et « avec quoi le stocker » (JSON pour les paramètres, SQLite pour les données croissantes, un usage minimal du Registre, Access uniquement pour l’intégration avec l’existant).

Sur cette base, quel que soit le format, intégrez dès la toute première version le trio de la section 6 — un numéro de version de format, un comportement dégradé en cas de corruption, et des sauvegardes restaurables. Les informations confidentielles sont, elles, toujours traitées à part via DPAPI. En gardant en tête le tableau de décision et les principes de conception communs de cet article, vous pouvez éviter presque toutes les configurations qui deviennent coûteuses après coup, comme « un fichier JSON qui a grossi jusqu’à plusieurs dizaines de Mo » ou « un fichier Access partagé qui se corrompt chaque semaine ». Si vous avez un doute sur la méthode de stockage d’une application existante, nous recommandons de commencer par un inventaire de ce qui est stocké où.

Articles connexes

Domaines de conseil associés

KomuraSoft LLC prend en charge la révision des méthodes de stockage de données des applications métier (y compris la conception de la migration depuis INI/XML/Access) ainsi que les investigations sur les causes de corruption des données et de dégradation des performances.

Références

  1. Microsoft Learn, KNOWNFOLDERID. Sur les définitions des dossiers connus de Windows tels que LocalAppData, RoamingAppData et ProgramData. 

  2. Microsoft Learn, Microsoft.Data.Sqlite overview. Sur la présentation du fournisseur ADO.NET pour SQLite maintenu par Microsoft, et son rôle de base pour le fournisseur EF Core SQLite.  2

  3. Microsoft Learn, Registry Redirector. Sur le mécanisme par lequel l’accès au Registre d’un processus 32 bits est redirigé vers Wow6432Node sous Windows 64 bits.  2

  4. Microsoft Learn, Can’t establish a connection to Access Database Engine OLE DB. Sur l’exigence que le nombre de bits du fournisseur ACE OLE DB corresponde à celui du processus qui y accède.  2

  5. Microsoft Learn, Environment.SpecialFolder Enum. Sur l’énumération utilisée pour obtenir les dossiers connus depuis .NET. 

  6. Microsoft Learn, Use a SQLite database in a Windows app. Tutoriel officiel recommandant SQLite avec Microsoft.Data.Sqlite / EF Core pour le stockage local des données des applications Windows. 

  7. SQLite, How To Corrupt An SQLite Database File. Sur le fait que les défaillances de verrouillage sur un système de fichiers réseau constituent une cause majeure de corruption de base de données. 

  8. Microsoft Learn, Data types (Microsoft.Data.Sqlite). Sur les quatre types primitifs de SQLite et la convention selon laquelle DateTime et Guid sont mappés en TEXT. 

  9. Microsoft, Microsoft Access Database Engine 2016 Redistributable. Sur le package redistribuable ACE (32 bits/64 bits) permettant d’accéder aux fichiers .accdb / .mdb. 

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Où une application de bureau Windows doit-elle stocker ses fichiers de données ?
Utilisez %LOCALAPPDATA%\NomSociété\NomApplication (Environment.SpecialFolder.LocalApplicationData) par défaut pour les paramètres et données propres à chaque utilisateur, et %PROGRAMDATA% pour les données partagées entre tous les utilisateurs — en laissant l'installateur créer le dossier et configurer explicitement la LCA (liste de contrôle d'accès). N'écrivez jamais dans le dossier contenant l'exécutable : les utilisateurs standard ne peuvent pas écrire sous Program Files, et sur les anciennes applications 32 bits, la couche de compatibilité de l'UAC peut rediriger silencieusement les écritures vers VirtualStore. Évitez également de faire du profil Roaming votre choix par défaut, car tout ce qu'il contient est synchronisé à l'ouverture et à la fermeture de session dans les environnements de domaine à profils itinérants.
Faut-il enregistrer les données de mon application en JSON ou en SQLite ?
Il n'existe en réalité que deux premiers choix : un fichier JSON pour des paramètres petits et structurés que vous lisez intégralement au démarrage — à titre indicatif, jusqu'à quelques centaines de Ko — et SQLite pour des données métier, un historique ou toute donnée croissante que vous devez rechercher ou agréger. Ces deux options couvrent l'immense majorité des besoins de stockage local des applications métier. Pour JSON, prémunissez-vous contre la corruption en écrivant dans un fichier temporaire puis en le remplaçant avec File.Replace ; dès que vous commencez à y stocker un historique qui s'accumule en continu, c'est le signal qu'il faut migrer vers SQLite.
Microsoft Access (.accdb) reste-t-il un choix raisonnable pour une base de données locale ?
Il ne reste presque plus aucune raison de choisir Access pour un nouveau projet. Lire un .accdb depuis du code nécessite le fournisseur ACE, et la connexion échoue si le nombre de bits du fournisseur ne correspond pas à celui de l'application — « le fournisseur Microsoft.ACE.OLEDB.12.0 n'est pas enregistré » chez un client est un cas de support classique — et il faut en plus distribuer un package redistribuable. Lorsque l'intégration avec des actifs Access existants est incontournable, fixez le nombre de bits du processus qui y accède, évitez les écritures concurrentes multi-utilisateurs sur un .accdb situé dans un dossier partagé, et gardez une trajectoire de migration à long terme vers SQLite ou une base de données serveur.
Comment sauvegarder une base SQLite en toute sécurité ?
Ne faites jamais de simple copie de fichier pendant que l'application est en cours d'exécution — une copie naïve d'une base de données active peut capturer des incohérences entre le fichier WAL et le fichier principal. Utilisez plutôt VACUUM INTO, qui prend un instantané cohérent en une seule instruction SQL, vers un dossier de sauvegarde versionné, et ne conservez que les N générations les plus récentes. Tout aussi important : répétez au moins une fois la restauration. Des fichiers de sauvegarde qui existent mais que personne n'a jamais restaurés sont une situation classique des systèmes métier, et une répétition révèle généralement des failles telles que des identifiants protégés par DPAPI qui ne se transfèrent pas vers une autre machine.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog