Wie Windows-Apps lokale Daten speichern sollten — Entscheidungstabelle für SQLite / JSON / Registrierung / Access
· Go Komura · SQLite, Windows, .NET, C#, Datenspeicherung, Registrierung, Access, Architektur, Entscheidungstabelle, Technische Beratung
„Reicht für die Einstellungen eine INI-Datei?“ „Die Verlaufsdaten sind gewachsen, wir wollen sie in Access ablegen.“ „Wie teilen wir die Aufgaben zwischen Registrierung und Einstellungsdatei auf?“ Wer Windows-Fachanwendungen entwickelt, kommt an der Wahl des Datenspeicherorts nicht vorbei. Diese Entscheidung wird jedoch oft zu Beginn eines Projekts eher beiläufig getroffen und nie wieder überprüft — bis Jahre später Probleme auftauchen: „Unsere JSON-Datei ist auf mehrere Dutzend MB angewachsen, der Start dauert lange“, „die Access-Datei im Freigabeordner geht etwa einmal pro Woche kaputt“ oder „wir schreiben direkt unter Program Files, und es funktioniert unter Windows 11 nicht mehr“.
Dieser Artikel ordnet die lokale Datenspeicherung von Windows-Fachanwendungen, indem er „wo man sie ablegt“ (Wahl des Ordners) von „womit man sie speichert“ (Wahl von Format bzw. Engine) trennt. Im Format der Entscheidungstabelle, das wir in diesem Blog schon mehrfach verwendet haben, fassen wir die Stärken und Fallstricke von SQLite, JSON, der Registrierung und Access zusammen.
1. Das Wichtigste zuerst
- Die Wahl des Speicherorts ist eigentlich eine Entscheidung in zwei unabhängigen Dimensionen: „wo man sie ablegt“ und „womit man sie speichert“. Bei der ersten führt ein Fehler zu Berechtigungs- und Mehrbenutzer-Vorfällen, bei der zweiten zu Beschädigung, Leistungs- und Wartungsproblemen.
- Die Grundregel für den Ablageort:
%LOCALAPPDATA%(Environment.SpecialFolder.LocalApplicationData) für benutzerbezogene Einstellungen und Daten,%PROGRAMDATA%für Daten, die von allen Benutzern gemeinsam genutzt werden, und niemals in den Ordner der ausführbaren Datei (unter Program Files) schreiben.1 - Bei den Formaten gibt es schlicht zwei erste Wahlmöglichkeiten. Kleine, strukturierte Einstellungen gehören in eine JSON-Datei, wachsende Geschäftsdaten, Verläufe oder alles, was durchsucht werden soll, in SQLite. Diese beiden Formate decken den Großteil der lokalen Speicherbedürfnisse von Fachanwendungen ab.2
- Die Registrierung ist „ein Ort für kleine Flags und Informationen zur Integration mit Windows selbst“ — sie ist kein Datenspeicher für Ihre App. Wer sie verwendet, ohne die 32-Bit/64-Bit-Registrierungsumleitung (
Wow6432Node) zu verstehen, stolpert über das Problem „der geschriebene Wert ist nicht da“.3 - Es gibt so gut wie keinen Grund mehr, Access (.accdb) als Datenspeicher für ein neues Projekt zu wählen. Selbst bei der Integration mit vorhandenen Beständen bleibt die Verteilungseinschränkung bestehen, dass die Bitness des ACE-Providers mit der der Anwendung übereinstimmen muss.4
- Unabhängig vom Format gilt: Nur vertrauliche Informationen (Passwörter, API-Schlüssel) brauchen eine gesonderte Behandlung. Legen Sie sie nicht im Klartext in JSON oder der Registrierung ab, sondern schützen Sie sie mit DPAPI. Stellen Sie sich DPAPI (Data Protection API) als eine Betriebssystemfunktion vor, die Ihnen die Verwaltung der Verschlüsselungsschlüssel abnimmt und Windows überlässt. Die App übergibt den Klartext an
ProtectedData.Protectund erhält verschlüsselte Bytes zurück — gespeichert wird ausschließlich dieses Byte-Array. Der Schlüssel ist an den angemeldeten Benutzer (oder den betreffenden Rechner) gebunden und wird vom Betriebssystem verwaltet, sodass die App keinen Schlüssel selbst einbetten muss. Umgekehrt bedeutet das: Die Entschlüsselung ist grundsätzlich nur auf demselben Benutzer und demselben Rechner möglich — das wirkt sich auf die Gestaltung von Gerätewechseln und Backups aus (Abschnitt 6.3).5 Details zur Verwendung finden Sie im separaten Artikel „Geheimnisse in Windows-Apps speichern – Klartextkonfiguration mit DPAPI vermeiden“.
2. Die Daten in vier Kategorien einteilen
Bevor Sie entscheiden, womit Sie speichern, klassifizieren Sie die Art der Daten, die Sie speichern wollen. Lokale Daten von Fachanwendungen lassen sich grob in vier Kategorien einteilen.
| Kategorie | Beispiele | Merkmale |
|---|---|---|
| Einstellungen | Verbindungsziele, Bildschirmlayout, zuletzt geöffneter Ordner | Klein. Wird beim Start vollständig gelesen. Manchmal möchten Benutzer sie direkt bearbeiten |
| Geschäftsdaten/Verlauf | Messergebnisse, Verarbeitungsverlauf, lokale Kopien von Stammdaten | Wächst stetig. Muss durchsucht/aggregiert werden können. Beschädigung hat große geschäftliche Auswirkungen |
| Cache | Miniaturansichten, heruntergeladene Ressourcen | Kann bei Verlust neu erzeugt werden. Erfordert Kapazitätsmanagement |
| Vertrauliche Informationen | Gespeicherte Passwörter, Tokens | Geringer Umfang. Darf niemals im Klartext gespeichert werden |
Der Kerngedanke dieses Artikels ist, dass sich der richtige Ort und das richtige Format für jede dieser Kategorien unterscheiden. Wenn Ihre App zu den Anwendungen gehört, bei denen „Einstellungen und Verlauf komplett in einer einzigen XML-Datei stecken“, ist diese Klassifizierung neu vorzunehmen der erste Schritt zur Verbesserung.
3. Wo ablegen — Grundlagen der Ordnerwahl
In .NET orientieren Sie sich an den Orten, die sich über Environment.GetFolderPath ermitteln lassen.6
| Ort | Ermittlung | Verwendungszweck |
|---|---|---|
%LOCALAPPDATA%\Firmenname\Appname |
SpecialFolder.LocalApplicationData |
Standard für benutzerbezogene Daten. Hier fängt man an |
%APPDATA%\Firmenname\Appname (Roaming) |
SpecialFolder.ApplicationData |
Nur für Einstellungen, die dem Benutzer in einer Umgebung mit servergespeicherten Profilen folgen sollen |
%PROGRAMDATA%\Firmenname\Appname |
SpecialFolder.CommonApplicationData |
Von allen Benutzern gemeinsam genutzte Daten. Erfordert ACL-Design |
| Unterhalb von Dokumente | SpecialFolder.MyDocuments |
Nur für Ergebnisse, die der Benutzer als seine eigenen Dateien betrachtet (z. B. exportierte Berichte) |
Im Code ist das schnell erledigt, aber wenn Sie die Verschachtelung „Firmenname\Appname“ und das Anlegen des Ordners beim ersten Start in eine gemeinsame Hilfsfunktion packen, verhindern Sie, dass Speicherorte unkontrolliert wuchern.
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); // Keine Aktion, falls bereits vorhanden
return dir;
}
}
Environment.GetFolderPath statt einer String-Verkettung der Umgebungsvariable %LOCALAPPDATA% zu verwenden, hat den Grund, dass die Methode auch unter einem Dienstkonto, einem anderen Benutzer oder in einer Umgebung mit konfigurierter Ordnerumleitung den korrekten Ort zurückliefert. Damit vermeiden Sie auch den Vorfall, dass sich der Pfad ändert, sobald die App über den Taskplaner unter einem anderen Konto läuft (eine Variante des im Taskplaner-Artikel in Abschnitt 5 beschriebenen Problems „funktioniert nur bei manueller Ausführung“).
Hier sind drei Fallstricke.
- Nicht in den Ordner der ausführbaren Datei schreiben. Unter Program Files können Standardbenutzer nicht schreiben. Bei älteren 32-Bit-Apps kann die Dateivirtualisierung, eine Kompatibilitätsfunktion von UAC (User Account Control, Benutzerkontensteuerung), Schreibvorgänge stillschweigend nach
VirtualStoreumleiten — das rätselhafte Symptom, dass „der Inhalt der Einstellungsdatei unterschiedlich ist, je nachdem ob die App als Administrator oder als Standardbenutzer ausgeführt wurde“. - ProgramData ist „beschreibbar, aber nicht sicher“. Mit der Standard-ACL kann es vorkommen, dass eine von einem Benutzer erstellte Datei von einem anderen Benutzer nicht geändert werden kann. Wenn alle Benutzer gemeinsam lesen und schreiben sollen, lassen Sie den Ordner vom Installer anlegen und die ACL dort explizit konfigurieren.
- Roaming nicht zum Standard machen. In Domänenumgebungen mit servergespeicherten Profilen wird alles unter Roaming bei An- und Abmeldung synchronisiert. Große Daten oder maschinenspezifische Daten (Caches, Hardware-Einstellungen) in Roaming abzulegen, führt zu Synchronisierungsverzögerungen oder zu „Verunreinigung“ auf anderen Rechnern. Im Zweifel: Local verwenden.
3.1 Wie Schreibvorgänge zu VirtualStore umgeleitet werden
Nur für den ersten Fallstrick zeigen wir den Mechanismus als Diagramm. UACs Dateivirtualisierung ist eine Kompatibilitätsfunktion: Versucht eine 32-Bit-App ohne requestedExecutionLevel im Manifest, in einen geschützten Ort wie Program Files zu schreiben, wird der Schreibvorgang nicht mit einem Fehler abgewiesen, sondern stillschweigend in das Benutzerprofil umgeleitet. Da auch beim Lesen der virtualisierte Ort bevorzugt wird, sieht es für den Schreibenden selbst so aus, als sei „ordnungsgemäß geschrieben worden“.7
flowchart TD
A["Die App schreibt unter Program Files<br/>in settings.ini"] --> B{"Besteht Schreibberechtigung?"}
B -- "Als Administrator ausgeführt" --> C["Wird am vorgesehenen Ort geschrieben"]
B -- "Standardbenutzer" --> D{"64-Bit, oder ist<br/>requestedExecutionLevel angegeben?"}
D -- "Ja" --> E["Schlägt sauber mit Zugriff verweigert fehl"]
D -- "Nein = nicht erhöhter 32-Bit-Prozess" --> F["UAC-Dateivirtualisierung greift ein"]
F --> G["Unter VirtualStore in LOCALAPPDATA<br/>entsteht eine Kopie pro Benutzer"]
G --> H["Beim Lesen hat die virtualisierte Kopie Vorrang<br/>— für den Schreibenden sieht es aus, als hätte es geklappt"]
C --> I["Als Administrator und als Standardbenutzer ausgeführt<br/>wird jeweils eine andere Datei gelesen"]
H --> I
Das tatsächliche Umleitungsziel ist %LOCALAPPDATA%\VirtualStore\Program Files\.... Da die Virtualisierung für jeden Benutzer eine eigene Kopie anlegt, zeigt sich das Problem auch in der Form, dass „auf Herrn A.s Rechner die Einstellung erhalten bleibt, aber auf einem gemeinsam genutzten Rechner nach der Anmeldung einer anderen Person wieder der Ausgangswert erscheint“. Es handelt sich ausschließlich um eine vorübergehende Rettungsfunktion für Legacy-Apps, die bei 64-Bit-Prozessen, erhöhten Prozessen und Prozessen mit Manifest nicht greift.7 Der entsprechende Mechanismus auf Seiten der Registrierung (HKLM\Software wird nach HKCU\Software\Classes\VirtualStore umgeleitet) ist ausführlich in „Registry-32-Bit/64-Bit-Umleitung und Virtualisierungsfallen — Wow6432Node und das Problem „Der Wert, den ich geschrieben habe, ist nicht da““ beschrieben.
3.2 Auf echter Hardware prüfen, ob geschrieben werden kann
Solange Sie nur mit dem Administratorkonto auf dem Entwicklungsrechner testen, ist das Design des Ablageorts nicht wirklich verifiziert. Führen Sie vor der Veröffentlichung mindestens diese drei Prüfungen durch.
- Als Standardbenutzer ausführen. Legen Sie auf dem Entwicklungsrechner ein lokales Standardbenutzerkonto an, melden Sie sich damit an und durchlaufen Sie einmal komplett Installation, Start, Speichern und Neustart. Nur bei einem Administratorkonto „Als Administrator ausführen“ abzuwählen reicht nicht: Zwar wird dadurch der Verzicht auf die UAC-Erhöhung nachgebildet, aber die Tatsache, dass dieser Benutzer weiterhin zur Administratorgruppe gehört, ändert sich dadurch nicht — als ACL-Prüfung ist das unzureichend.
- Mit Process Monitor prüfen, wohin tatsächlich geschrieben wird. Filtern Sie auf den Prozess der App und die Operationen
CreateFile/WriteFile. Prüfen Sie, ob der beabsichtigte OrtACCESS DENIEDliefert und ob in der SpaltePathein Pfad mitVirtualStoreauftaucht. Da Procmon den tatsächlichen Pfad nach Auflösung der Umleitung anzeigt, sehen Sie die Abweichung zwischen „vermeintlich geschriebenem Ort“ und „tatsächlich geschriebenem Ort“ unmittelbar. Zur Verwendung siehe „Process Monitor (ProcMon) Praxisleitfaden — „Konfiguration wird nicht gelesen“ und ACCESS DENIED in 10 Minuten identifizieren“. - Die ACL lesen. Prüfen Sie mit
icacls "C:\ProgramData\KomuraSoft\MyApp", ob die vorgesehenen Benutzer und Gruppen Schreibrechte haben. Wenn Sie ProgramData als von allen Benutzern gemeinsam genutzten Lese-/Schreibbereich einsetzen, prüfen Sie hier auch, ob die vom Installer gesetzte ACL tatsächlich erscheint.
Bestehen diese drei Prüfungen, vermeiden Sie zumindest die Art von Vorfall, bei der die App unmittelbar nach der Auslieferung nicht speichern kann.
4. Womit speichern — die vier Optionen im Überblick
4.1 JSON-Dateien — die erste Wahl für Einstellungen
JSON lässt sich mit System.Text.Json unkompliziert lesen und schreiben, ist für Menschen lesbar und lässt sich gut mit Git verwalten und per Diff vergleichen — alles Vorteile, die für den Einstellungszweck zusammenkommen. Es gibt zwei Dinge zu beachten.
Sorgen Sie für Schutz vor Beschädigung. Wenn während des Schreibens der Strom ausfällt, kann eine halb geschriebene Datei zurückbleiben, die beim nächsten Start nicht mehr lesbar ist. Die bewährte Methode ist „in eine temporäre Datei schreiben und dann ersetzen“ — in .NET bietet File.Replace ein Ersetzen mit Sicherungskopie.
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);
Wenn Sie auch beim Einlesen von Anfang an ein Degradationsverhalten vorsehen — „bei Beschädigung .bak versuchen, und wenn auch das scheitert, mit Standardwerten starten und warnen“ —, wird eine beschädigte Einstellungsdatei nicht mehr zum Supportfall.
Machen Sie daraus keinen Datenspeicher. JSON eignet sich für Größenordnungen, bei denen „beim Start alles lesen, beim Beenden alles schreiben“ noch trägt — als Richtwert bis zu einigen hundert KB. Sobald Sie beginnen, kontinuierlich angehängten Verlauf oder Daten, die eine Datensatzsuche benötigen, in JSON zu packen, ist das Ihr Signal, zu SQLite zu wechseln.
4.2 SQLite — die erste Wahl für wachsende, durchsuchbare Daten
SQLite ist eine serverlose, dateibasierte, gemeinfreie eingebettete Datenbank. Aus .NET heraus können Sie sie über Microsoft.Data.Sqlite, den von Microsoft gepflegten ADO.NET-Provider, oder über den SQLite-Provider von EF Core ansprechen.2 Microsoft selbst empfiehlt SQLite als Mittel zur lokalen Datenspeicherung in Windows-Apps,8 sodass es durchaus angemessen ist, bei „lokal wachsenden strukturierten Daten“ zuerst an SQLite zu denken.
Zunächst zeigen wir im Code, wie unkompliziert das ist. Fügen Sie Microsoft.Data.Sqlite per NuGet hinzu, und Sie können sofort loslegen, indem Sie nur einen Dateipfad angeben — kein Server-Setup, keine Verwaltungsoberfläche für Verbindungszeichenfolgen nötig.
using Microsoft.Data.Sqlite;
var dbPath = Path.Combine(AppPaths.DataDir, "app.db");
using var conn = new SqliteConnection($"Data Source={dbPath}");
conn.Open();
// Nur beim ersten Mal: WAL-Modus aktivieren und Tabellen anlegen
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();
}
// Für Inserts sind Parameter Pflicht (SQL nicht per Stringverkettung zusammenbauen)
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();
}
Sie sehen: Für kaum mehr Aufwand als „an eine JSON-Datei anzuhängen“ erhalten Sie indizierte Suche, Aggregation und einen der Anzahl nach unbegrenzten Verlauf. Wenn Sie eine ORM-Schicht möchten, setzt der EF-Core-SQLite-Provider auf dieser Bibliothek auf.
Darauf aufbauend folgen die wichtigsten praktischen Punkte.
- WAL-Modus aktivieren. Das ist das
PRAGMA journal_mode=WAL;im obigen Code. WAL steht für Write-Ahead Logging (vorausschauendes Schreiben in ein Protokoll) und bezeichnet ein Verfahren, bei dem Änderungen nicht direkt in die Hauptdatenbankdatei geschrieben, sondern an eine daneben angelegte-wal-Datei angehängt und später gesammelt in die Hauptdatei übernommen werden. Da der Schreibende nur am Ende anhängt, stört er die Lesenden nicht — Lesen und Schreiben können gleichzeitig ablaufen.9 Deshalb kommt es seltener zu Blockaden, wenn UI-Thread und Hintergrundverarbeitung dieselbe Datenbank anfassen. Die WAL-Einstellung wird in der Datenbankdatei selbst dauerhaft gespeichert, Sie müssen sie also nicht bei jeder Verbindung erneut setzen (einmal gesetzt, bleibt sie auch nach Schließen und erneutem Öffnen erhalten).9 Als Nebeneffekt besteht die Datenbank nicht mehr aus einer einzigen Datei: Nebenapp.dbentstehenapp.db-walundapp.db-shm. Das ist der Grund, warum eine Sicherung, die während des Betriebs nurapp.dbkopiert, riskant ist (Abschnitt 6.3). - Schreibvorgänge auf einen Pfad pro Prozess konzentrieren. Schreibzugriffe von SQLite sind auf Datenbankebene exklusiv. Wenn mehrere Threads schreiben müssen, ist ein Design sicherer, das die Schreibrolle über eine Warteschlange bündelt. Wenn Sie außerdem viele kleine INSERTs ausführen, macht das Zusammenfassen in eine explizite Transaktion den Vorgang um Größenordnungen schneller, als jeden Datensatz einzeln zu committen.
- Nicht auf einer Netzwerkfreigabe ablegen. Dateisperren über SMB sind häufig von der Umgebung abhängig und fehleranfällig, und auch das SQLite-Projekt selbst nennt die gemeinsame Nutzung über ein Netzwerkdateisystem als Hauptursache für Beschädigung.10 Der WAL-Modus setzt ohnehin voraus, dass „die Prozesse, die dieselbe Datenbank verwenden, auf demselben Rechner laufen“, und funktioniert auf einem Netzwerkdateisystem nicht (weil dabei gemeinsamer Speicher zwischen Prozessen verwendet wird).9 Sobald Sie gleichzeitig von mehreren Rechnern oder Benutzern zugreifen möchten, sind Sie bereits im Bereich einer Client-Server-Datenbank (z. B. SQL Server Express).
- Wissen, dass es nur vier Typen gibt. Intern kennt SQLite nur INTEGER / REAL / TEXT / BLOB, und Datumswerte sowie GUIDs werden als TEXT gespeichert. Wenn Sie sich einmal die Typzuordnungsregeln von
Microsoft.Data.Sqliteansehen, ersparen Sie sich später Kopfzerbrechen beim Vergleichen und Sortieren von Datumswerten.11 Dass im obigen Beispielcreated_atmitdatetime('now')(UTC) gesetzt wird, liegt daran, dass das Mischen mit lokaler Zeit beim Sortieren und beim Wechsel der Sommerzeit Ärger macht. Die Umrechnung in lokale Zeit erst bei der Anzeige ist die sicherere Vorgehensweise. - Sicherungen über
VACUUM INTOoder die Backup API erstellen, nicht per Dateikopie. Eine einfache Kopie der laufenden Datenbankdatei kann Inkonsistenzen zwischen WAL und Hauptdatei aufgreifen (Details in Kapitel 6).
4.3 Registrierung — nur für kleine Flags und Windows-Integrationsinformationen
Die Registrierung eignet sich für Informationen zur Integration mit Windows selbst — „ist installiert oder nicht“, „Autostart-Registrierung“, „Dateizuordnungen“ — und für sehr kleine Benutzereinstellungen, nicht mehr. Als Grundregel gilt: Einstellungen, die die App für sich selbst nutzt, gehören unter HKCU, maschinenweite Informationen schreibt der Installer nach HKLM (ein Design, das zur Laufzeit nach HKLM schreibt, erfordert Administratorrechte und sollte vermieden werden).
Der größte Fallstrick ist die Bitness. Unter 64-Bit-Windows wird HKLM\Software, wie es ein 32-Bit-Prozess sieht, nach HKLM\Software\Wow6432Node umgeleitet.3 Symptome wie „im Registrierungs-Editor ist der Wert da, aber die App kann ihn nicht lesen“ oder „ein von der 32-Bit-App geschriebener Wert ist im 64-Bit-Wartungswerkzeug nicht sichtbar“ sind fast immer darauf zurückzuführen. Das tritt typischerweise beim Umstieg auf AnyCPU oder bei der Umstellung auf 64 Bit zutage, behalten Sie es also im selben Zusammenhang wie die 32-Bit/64-Bit-Probleme bei COM und ActiveX im Kopf („Registrierungs- und Bitness-Fallen bei der COM/OCX/ActiveX-Entwicklung“).
Wenn Sie aus .NET heraus unbedingt die Ansicht der anderen Bitness lesen müssen (etwa eine weiterhin als 32-Bit gepflegte App, die einen auf der 64-Bit-Seite registrierten Wert lesen soll), können Sie die Ansicht mit RegistryView explizit angeben.
using Microsoft.Win32;
// Die 64-Bit-Ansicht von HKLM aus einem 32-Bit-Prozess lesen
using var hklm64 = RegistryKey.OpenBaseKey(
RegistryHive.LocalMachine, RegistryView.Registry64);
using var key = hklm64.OpenSubKey(@"SOFTWARE\KomuraSoft\MyApp");
var installDir = key?.GetValue("InstallDir") as string;
Umgekehrt ist die Notwendigkeit dieser Angabe zugleich ein Zeichen dafür, dass Sie die Designentscheidung „ist 32 Bit oder 64 Bit die korrekte Seite zum Schreiben“ aufgeschoben haben. Der richtige Weg ist, die Bitness von Schreib- und Lesezugriff aneinander anzugleichen.
Daten über wenige KB hinaus oder listenartige Daten in der Registrierung abzulegen, ist in jeder Hinsicht nachteilig — bei Backup, Migration und Diagnose gleichermaßen. Überlassen Sie diesen Zweck Dateien (JSON / SQLite).
4.4 Access (.accdb) — für Neuentwicklungen kaum noch sinnvoll, bei Bestandsintegration mit Augenmaß
Früher war Access (JET/ACE) die naheliegende Wahl für die lokale Datenbank einer Fachanwendung, aber heute gibt es für Neuentwicklungen kaum noch einen Grund dafür. Der Hauptgrund ist die Verteilung. Um von Code aus auf eine .accdb zuzugreifen, benötigen Sie den ACE-Provider (Access Database Engine), und die Verbindung schlägt fehl, wenn die Bitness der App nicht mit der von ACE übereinstimmt.4 Hinzu kommen Koexistenzprobleme mit der Bitness von Office, und „funktioniert auf dem Entwicklungsrechner, aber beim Kunden erscheint Der Provider 'Microsoft.ACE.OLEDB.12.0' ist nicht auf dem lokalen Computer registriert“ ist ein klassischer Supportfall. Auch dass ein Redistributable-Paket (Access Database Engine 2016 Redistributable) installiert werden muss, vergrößert die Liste der Auslieferungsartefakte.12
Trotzdem gibt es in der Praxis Situationen, in denen Access unvermeidlich ist — Datenintegration mit einem bestehenden Access-Fachsystem, das Lesen von in Access erstellten Stammdaten und Ähnliches. In diesen Fällen empfiehlt sich,
- die Bitness des lesend/schreibend zugreifenden Prozesses festzulegen (in der Praxis oft x86) und den Installer die Existenz des passenden ACE prüfen zu lassen,
- gleichzeitiges Schreiben mehrerer Personen auf eine .accdb in einem Freigabeordner als Designentscheidung zu vermeiden (die Wiederherstellungskosten im Schadensfall lohnen sich nicht),
- und langfristig einen Migrationspfad zu SQLite oder einer serverbasierten Datenbank offenzuhalten
— diese Kompromisse sind der pragmatische Weg. Der Umgang mit vorhandenen Beständen einschließlich Excel/VBA wird auch in „Was ist VBA? - Grenzen, Zukunftsaussichten und wann man es ersetzen sollte“ behandelt.
5. Entscheidungstabelle
5.1 Schnellübersicht: Kategorie × Format
Zunächst eine Tabelle, die die vier Kategorien aus Kapitel 2 den vier Formaten aus Kapitel 4 gegenüberstellt. Sie dient dazu, in einer Zeile nachzuschlagen: „Meine Daten gehören in diese Kategorie, also dieses Format und dieser Ablageort“.
| Kategorie (Kapitel 2) | JSON-Datei | SQLite | Registrierung | Access | Standard-Ablageort (Kapitel 3) |
|---|---|---|---|---|---|
| Einstellungen | ◎ Erste Wahl | ○ Bei absehbarem Wachstum von Anfang an hierher | △ Nur ganz kleine Flags und Windows-Integrationsinformationen | ✕ | %LOCALAPPDATA% (nur Einstellungen, die dem Benutzer folgen sollen: Roaming) |
| Geschäftsdaten/Verlauf | ✕ Die Annahme des vollständigen Einlesens bricht zusammen | ◎ Erste Wahl | ✕ | △ Nur bei Integration mit bestehenden Access-Beständen | %LOCALAPPDATA% (bei gemeinsamer Nutzung durch alle: %PROGRAMDATA% + ACL-Design) |
| Cache | △ Nur bei geringer Größe | ○ Bei vielen Einträgen | ✕ | ✕ | %LOCALAPPDATA% (nicht in Roaming ablegen) |
| Vertrauliche Informationen | ○ Als Behälter für DPAPI-geschützte Werte | ○ Dasselbe | △ Dasselbe, aber nur bei geringer Größe | ✕ | Nicht im Klartext ablegen. Mit DPAPI schützen (Kapitel 1) |
Zwei Lesehinweise. Erstens: Packen Sie nicht mehrere Zeilen in einen einzigen Behälter. „Einstellungen, Verlauf und Cache alle in einer einzigen JSON-Datei“ ist das typische Design, das sich später rächt. Zweitens: Bei der Zeile für vertrauliche Informationen kommt es weniger darauf an, „in welches Format“ sie gehören, als darauf, „ob sie vor dem Speichern mit DPAPI verschlüsselt wurden“ — die Wahl des Behälters können Sie ansonsten nach den übrigen drei Kategorien treffen.
5.2 Eigenschaften der einzelnen Formate
| Aspekt | JSON-Datei | SQLite | Registrierung | Access (.accdb) |
|---|---|---|---|---|
| Geeignete Daten | Kleine Einstellungen | Wachsende strukturierte Daten, Suche/Aggregation | Kleine Flags, Windows-Integration | Integration mit bestehenden Access-Beständen |
| Typischer Datenumfang | Bis zu einigen hundert KB | Bis zu einigen zehn GB | Bis zu einigen KB | Bis zu 2 GB (Spezifikationsgrenze) |
| Suche/Aggregation | ✕ (setzt vollständiges Einlesen voraus) | ◎ (SQL) | ✕ | ○ (SQL) |
| Direkt für Menschen lesbar | ◎ | △ (Werkzeug nötig) | △ | △ (Access nötig) |
| Widerstandsfähigkeit gegen Beschädigung | △ (eigene Vorkehrungen nötig) | ○ (Transaktionen) | ○ | △ |
| Gleichzeitiger Zugriff mehrerer Prozesse | ✕ | ○ (innerhalb derselben Maschine) | ○ | △ |
| Gemeinsame Nutzung über mehrere Maschinen | ✕ | ✕ | ✕ | ✕ (faktisch) |
| Zusätzliche Auslieferungsartefakte | Keine | Keine (in NuGet enthalten) | Keine | ACE-Provider erforderlich |
Wie die letzte Zeile zeigt, ist keine dieser lokalen Speichertechnologien für die „gemeinsame Nutzung über mehrere Maschinen“ geeignet. Es sieht so aus, als würde die Ablage in einem Freigabeordner Freigabe ermöglichen, aber JSON hat keinen Exklusionsmechanismus, die Sperren von SQLite über SMB sind nicht zuverlässig, und Access stößt zusammen mit dem Beschädigungsrisiko an seine Grenzen. Sobald die Anforderung entsteht, dass mehrere Standorte oder mehrere Benutzer dieselben Daten anfassen, betrachten Sie das als die Grenze, an der Sie eine serverbasierte Datenbank wie SQL Server Express oder eine Web-API aufsetzen sollten.
6. Beschädigungsresistent, migrierbar, wiederherstellbar — formatübergreifende Designpunkte
Unabhängig vom gewählten Format gibt es drei Designelemente, die Sie zwangsläufig brauchen, wenn Sie die App über mehrere Jahre betreiben. Ob Sie sie in die erste Version aufnehmen oder nicht, macht bei den späteren Wartungskosten einen erheblichen Unterschied.
6.1 Schema/Format mit einer Versionsnummer versehen
Wird die App aktualisiert, ändert sich auch die Form der gespeicherten Daten. Der Moment, in dem „die neue Version Daten liest, die eine alte Version geschrieben hat“, kommt unweigerlich, geben Sie den Daten also eine Formatversion.
Bei SQLite ist PRAGMA user_version genau für diesen Zweck vorgesehen.
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)
// Fall: Eine neuere Version der App hat die DB erstellt, eine alte App öffnet sie.
// Sicherer, hier zu stoppen, statt ein unbekanntes Schema anzufassen
throw new InvalidOperationException(
$"Diese Datenbank (Version {v}) wurde mit einer neueren Version der App erstellt.");
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();
}
Das ist die minimale Form dessen, was man üblicherweise Migration nennt: Beim Start die Version prüfen und nur die Differenz anwenden. Dass eine „neuere Version als die eigene“ von vornherein abgelehnt wird, dient dazu, den Vorfall zu verhindern, dass beim Zurücksetzen der App auf eine ältere Version (Rollback) der alte Code in ein ihm unbekanntes Schema schreibt und es beschädigt. Bei JSON gilt derselbe Gedanke: Geben Sie der Wurzel ein "version": 2, fügen Sie beim Einlesen eine Umwandlung aus älteren Formaten ein und verweigern Sie das Einlesen zu neuer Formate. „Kein Datenformat ohne Versionsnummer veröffentlichen“ — allein diese Regel rettet Ihr zukünftiges Ich.
6.2 Das Degradationsverhalten bei Beschädigung festlegen
In Kapitel 4 wurden formatspezifische Schutzmaßnahmen gegen Beschädigung angesprochen (atomare Schreibvorgänge bei JSON, Transaktionen bei SQLite), aber trotzdem werden Sie irgendwann auf „nicht lesbare Daten“ stoßen — Festplattenfehler, Quarantäne durch einen Fehlalarm der Antivirensoftware, manuelle Bearbeitung durch Benutzer. Wenn Sie nicht festgelegt haben, wie sich die App in diesem Fall verhält, endet das in einer App, die nicht einmal startet.
- Einstellungen nicht lesbar → mit Standardwerten starten und den Benutzer darüber informieren (stilles Zurückfallen auf Standardwerte führt zu Anfragen wegen „verschwundener Einstellungen“).
- Geschäftsdaten nicht lesbar → über einen schreibgeschützten Modus oder einen Fehlerbildschirm anzeigen, welche Datei beschädigt ist. Nicht automatisch durch Überschreiben reparieren (dabei geht der Beweis verloren).
- Backup vorhanden → die Wiederherstellung anbieten. Eine automatische Wiederherstellung birgt jedoch das Risiko, fälschlich eine Beschädigung zu erkennen und auf alte Daten zurückzufallen, daher grundsätzlich eine Benutzeraktion dazwischenschalten.
6.3 Bei Backups zählt „lässt es sich wiederherstellen“ mehr als „ist eines vorhanden“
Anders als bei einer Server-Datenbank sichert lokale Daten niemand automatisch für Sie. Wenn sich die App selbst darum kümmern soll, legen Sie diese drei Punkte fest.
- Was: Geschäftsdaten gehören dazu, Caches nicht; bei vertraulichen Informationen berücksichtigen Sie, dass sie sich aufgrund von DPAPIs Eigenart nur auf demselben Benutzer und derselben Maschine entschlüsseln lassen (für den Wechsel auf eine andere Maschine ist ein separates Verfahren nötig).
- Wann/Wohin: beim Start oder täglich, mit Generationen versehen in einen
backup-Ordner unter%LOCALAPPDATA%. Ob zusätzlich ein Freigabeordner oder ein bestehender PC-Backup-Zielordner genutzt wird, ist Sache des Betriebs. - Wie: Bei SQLite ist eine einfache Dateikopie während des Betriebs tabu. Mit
VACUUM INTO 'backup.db'erhalten Sie mit einer einzigen Anweisung eine konsistente Momentaufnahme.
// VACUUM INTO legt den übergeordneten Ordner nicht an und schlägt fehl, wenn das Ziel bereits existiert.
// Ordner vorher anlegen und einen nicht kollidierenden Dateinamen festlegen
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();
Da sich die Generationen immer weiter ansammeln, bauen Sie im selben Zug nach jeder Sicherung einen Vorgang ein, der „nur die letzten N Generationen behält und ältere löscht“.
Und führen Sie mindestens einmal eine Wiederherstellungsprobe durch. Es ist ein klassisches Bild bei Fachsystemen: Backup-Dateien existieren, aber niemand weiß, wie man sie zurückspielt, oder es wurde nie ausprobiert. Wenn Sie eine Anleitung schreiben, wie Sie beim Austausch eines PCs Daten auf ein neues Gerät übertragen, finden Sie in der Regel Lücken im Backup-Design (DPAPI-geschützte Anmeldeinformationen, die nicht mitwandern, Pfade, die den Benutzernamen enthalten und unter einem anderen Benutzer brechen, und Ähnliches). Zum Löschen von Daten bei der Entsorgung eines PCs siehe auch „Was Sie vor der Entsorgung eines Windows-PCs erledigen sollten“.
7. Leitlinien für häufige Zweifelsfälle
- „Es ist eine Einstellung, wird aber wohl noch wachsen“ — Wenn absehbar ist, dass die Praxis „beim Start alles lesen“ nicht mehr trägt, setzen Sie von Anfang an auf SQLite. Eine „settings“-Tabelle in SQLite anzulegen, ist überhaupt nichts Schlechtes.
- „Migration von INI/XML“ — Wenn es nur um einen Formatwechsel geht, nach JSON; wenn zu diesem Zeitpunkt auch verlaufsartige Daten vermischt sind, diese abtrennen und nach SQLite überführen. Wenn Sie beim Einlesen für ein oder zwei Versionen einen Fallback auf das alte Format behalten, wird die Migration sicherer.
- „Man möchte es in Excel sehen können“ — Statt Excel/Access zum Datenspeicher zu machen, erfüllen Sie sowohl die Datenzuverlässigkeit als auch den Wunsch besser, indem Sie in SQLite speichern und eine Export-Funktion nach CSV/Excel hinzufügen. Zur Erstellung der Berichtsausgabe siehe „Excel-Berichtsausgabe erstellen - COM/Open XML/Vorlagen“.
- „Mehrere Prozesse sollen dieselbe Datei lesen und schreiben“ — Innerhalb derselben Maschine kommen Sie mit SQLite (WAL) recht weit, aber Sie müssen den Umgang mit Schreibkonflikten entwerfen. Wenn die Koordination dateibasiert erfolgen soll, verwenden Sie die Exklusionsmuster aus „Best Practices für Dateiintegration und Sperren“.
- „Wir wollen über mehrere Maschinen hinweg teilen“ — Damit verlassen Sie die lokale Speicherung. Erste Wahl ist eine Client-Server-Konfiguration mit SQL Server Express (kostenlos, bis zu 10 GB pro Datenbank) auf einer Maschine, die einem Dateiserver entspricht. Beachten Sie, dass SQL Server „LocalDB“ entgegen seinem Namen eine für die Entwicklung gedachte Einzelbenutzerumgebung ist — wählen Sie es nicht für Freigabezwecke. Sobald Sie standortübergreifend oder von außerhalb des Unternehmens zugreifen wollen, ist das die Grenze, ab der Sie eine Konfiguration mit vorgeschalteter Web-API in Betracht ziehen sollten.
8. Zusammenfassung
Wenn Sie die Wahl des Speicherorts in „wo ablegen“ (LocalAppData/ProgramData, und niemals Program Files) und „womit speichern“ (JSON für Einstellungen, SQLite für wachsende Daten, Registrierung nur minimal, Access nur bei Bestandsintegration) aufteilen, lässt sich in den meisten Fällen ohne Zögern entscheiden.
Nehmen Sie darüber hinaus, unabhängig vom Format, das Dreierset aus Kapitel 6 — Formatversionsnummer, Degradationsverhalten bei Beschädigung und wiederherstellbare Backups — schon in die erste Version auf. Nur vertrauliche Informationen erhalten immer eine gesonderte Behandlung über DPAPI. Wenn Sie die Entscheidungstabelle und die gemeinsamen Designpunkte aus diesem Artikel beherzigen, vermeiden Sie fast alle im Nachhinein teuren Konfigurationen wie „eine auf mehrere Dutzend MB angewachsene JSON-Datei“ oder „ein wöchentlich kaputtes gemeinsames Access“. Wenn Sie sich bei der Speicherweise einer bestehenden App unsicher sind, empfehlen wir, zunächst eine Bestandsaufnahme zu machen, was wo gespeichert wird.
Verwandte Artikel
- Geheimnisse in Windows-Apps speichern – Klartextkonfiguration mit DPAPI vermeiden
- Best Practices für Dateiintegration und Sperren
- Excel-Berichtsausgabe erstellen - COM/Open XML/Vorlagen
- Geplante Aufgaben mit dem Taskplaner sicher betreiben
Verwandte Beratungsleistungen
Die KomuraSoft LLC übernimmt die Überarbeitung von Datenspeicherverfahren für Fachanwendungen (einschließlich Migrationsdesign von INI/XML/Access) sowie die Ursachenuntersuchung von Datenbeschädigung und Leistungseinbußen.
- Technische Beratung & Design-Review
- Weiternutzung und Migration von Altbeständen
- Windows-Anwendungsentwicklung
- Kontakt
Referenzlinks
-
Microsoft Learn, KNOWNFOLDERID. Zu den Definitionen der bekannten Windows-Ordner wie LocalAppData, RoamingAppData und ProgramData. ↩
-
Microsoft Learn, Microsoft.Data.Sqlite overview. Zum Überblick über den von Microsoft gepflegten ADO.NET-Provider für SQLite und dazu, dass er die Grundlage für den EF-Core-SQLite-Provider bildet. ↩ ↩2
-
Microsoft Learn, Registry Redirector. Zum Mechanismus, mit dem der Registrierungszugriff von 32-Bit-Prozessen unter 64-Bit-Windows nach Wow6432Node umgeleitet wird. ↩ ↩2
-
Microsoft Learn, Can’t establish a connection to Access Database Engine OLE DB. Dazu, dass die Bitness des ACE-OLE-DB-Providers mit der des zugreifenden Prozesses übereinstimmen muss. ↩ ↩2
-
Microsoft Learn, DataProtectionScope Enum. Zur Definition des Schutzbereichs, der
ProtectedData.Protect/Unprotectübergeben wird. Dazu, dass beiCurrentUsernur Threads entschlüsseln können, die im aktuellen Benutzerkontext laufen, während beiLocalMachinejeder beliebige Prozess auf diesem Computer entschlüsseln kann, weshalb diese Option auf Fälle beschränkt bleiben sollte, in denen alle Konten dieser Maschine als vertrauenswürdig gelten, und in den meisten FällenCurrentUserverwendet werden sollte. ↩ -
Microsoft Learn, Environment.SpecialFolder Enum. Zur Aufzählung, mit der sich bekannte Ordner aus .NET heraus ermitteln lassen. ↩
-
Microsoft Learn, UAC Architecture. Dazu, dass die Datei-/Registrierungsvirtualisierung von UAC maschinenweite Schreibanforderungen an benutzerbezogene Orte umleitet und auch beim Lesen der virtualisierte Ort bevorzugt wird; dazu, dass beim Schreiben in geschützte Ordner wie Program Files eine Kopie im Benutzerprofil verwendet wird, wobei jeder Benutzer eine eigene Kopie erhält; dazu, dass die Virtualisierung nur 32-Bit-Apps betrifft und bei erhöhten Prozessen oder Apps mit Manifest und
requestedExecutionLeveldeaktiviert ist (ein nicht erhöhter 64-Bit-Prozess erhält stattdessen Zugriff verweigert); sowie dazu, dass es sich um eine vorübergehende Kompatibilitätsfunktion handelt, auf die man sich nicht verlassen sollte. ↩ ↩2 -
Microsoft Learn, Use a SQLite database in a Windows app. Das offizielle Tutorial, das SQLite zusammen mit Microsoft.Data.Sqlite/EF Core für die lokale Datenspeicherung in Windows-Apps empfiehlt. ↩
-
SQLite, Write-Ahead Logging. Zum Mechanismus, Änderungen statt in die Hauptdatei in die WAL-Datei anzuhängen; dazu, dass der Schreibende nur anhängt und deshalb gleichzeitig mit dem Lesenden arbeiten kann; dazu, dass
journal_mode=WALdauerhaft ist und auch nach erneutem Öffnen erhalten bleibt; dazu, dass die beiden zusätzlichen Dateien-walund-shmentstehen; sowie dazu, dass die Prozesse, die dieselbe Datenbank verwenden, auf derselben Maschine laufen müssen und WAL auf einem Netzwerkdateisystem nicht funktioniert. ↩ ↩2 ↩3 -
SQLite, How To Corrupt An SQLite Database File. Dazu, dass Sperrprobleme über Netzwerkdateisysteme eine Hauptursache für Datenbankbeschädigung sind. ↩
-
Microsoft Learn, Data types (Microsoft.Data.Sqlite). Zu den vier primitiven Typen von SQLite und der Konvention, DateTime und Guid auf TEXT abzubilden. ↩
-
Microsoft, Microsoft Access Database Engine 2016 Redistributable. Zum ACE-Redistributable-Paket (32-Bit/64-Bit) für den Zugriff auf .accdb/.mdb. ↩
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
Windows-Prozesskommunikation richtig wählen ── Eine Entscheidungstabelle für Named Pipes / TCP / gRPC / Shared Memory / COM
Wie wählt man den richtigen Weg, damit Windows-Anwendungen miteinander sprechen? Dieser Artikel ordnet Named Pipes, lokales TCP, gRPC, Sh...
SQLite aus C# in Business-Apps nutzen — WAL-Modus, exklusive Sperren, Schutz vor Datenbankbeschädigung und wann sich EF Core lohnt
Ein praxisnaher Überblick zur Einbindung von SQLite in eine Windows-Businessanwendung mit Microsoft.Data.Sqlite: Verbindungszeichenfolgen...
Wie man die Windows-Sitzungsisolierung versteht — Session 0, RDP und die gleichzeitige Nutzung durch mehrere Benutzer
Dieser Artikel erklärt das Konzept der Windows-„Sitzung“ (Session), das Windows-App-Entwickler immer wieder verwirrt. Er behandelt, warum...
Windows-Dienste erstellen und betreiben ── Von der Abgrenzung zur Aufgabenplanung bis zur Umwandlung eines BackgroundService in einen Dienst
Sollte ein Hintergrundprozess ein Windows-Dienst werden, oder reicht die Aufgabenplanung? Dieser Artikel ordnet die Entscheidungstabelle,...
Das Datenbankschema Ihrer Business-Anwendung versionieren ── Migrationspraxis gegen „jeder Kunde hat eine andere Datenbank“
Ein praktischer Leitfaden zur Versionierung des Datenbankschemas einer Business-Anwendung, deren Datenbanken über zahlreiche Kundenstando...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Wo sollte eine Windows-App ihre Einstellungsdateien speichern?
- Für benutzerbezogene Einstellungen und Daten ist die Grundregel, sie unter %LOCALAPPDATA% (Environment.SpecialFolder.LocalApplicationData) in der Verschachtelung „Firmenname\Appname“ abzulegen. Für die gemeinsame Nutzung durch alle Benutzer ist %PROGRAMDATA% vorgesehen, wobei die Standard-ACL so beschaffen sein kann, dass ein anderer Benutzer die Datei nicht ändern kann — konfigurieren Sie die ACL deshalb explizit im Installer. In den Ordner der exe (unter Program Files) dürfen Sie nicht schreiben: Standardbenutzer können dort nicht schreiben, und bei älteren 32-Bit-Apps führt die stillschweigende Umleitung nach VirtualStore zu rätselhaften Symptomen.
- Sollte ich Einstellungen als JSON oder in SQLite speichern?
- Kleine, strukturierte Einstellungen gehören in eine JSON-Datei, wachsende Geschäftsdaten, Verläufe oder alles, was durchsucht werden soll, in SQLite als erste Wahl — diese beiden Formate decken den Großteil der lokalen Speicherbedürfnisse von Fachanwendungen ab. Der Anwendungsbereich von JSON liegt als Richtwert bei bis zu einigen hundert KB, solange „beim Start alles lesen, beim Beenden alles schreiben“ noch trägt. Sobald Sie beginnen, kontinuierlich angehängten Verlauf oder Daten, die eine Datensatzsuche benötigen, in JSON zu packen, ist das das Signal zum Wechsel auf SQLite. Auch bei Einstellungen ist es völlig in Ordnung, bei absehbarem künftigem Wachstum von Anfang an eine settings-Tabelle in SQLite anzulegen.
- Darf ich eine SQLite-Datenbank auf einer Netzwerkfreigabe ablegen?
- Das sollten Sie vermeiden. Dateisperren über SMB sind häufig von der Umgebung abhängig, und auch das SQLite-Projekt selbst nennt die gemeinsame Nutzung über ein Netzwerkdateisystem als Hauptursache für Beschädigung. Da JSON keinen Exklusionsmechanismus besitzt und auch Access zusammen mit dem Beschädigungsrisiko an seine Grenzen stößt, ist keine dieser lokalen Speichertechnologien für die gemeinsame Nutzung über mehrere Maschinen geeignet. Sobald die Anforderung entsteht, dass mehrere Standorte oder mehrere Benutzer dieselben Daten anfassen, ist das die Grenze, an der Sie eine serverbasierte Datenbank wie SQL Server Express (kostenlos, bis zu 10 GB pro Datenbank) oder eine Web-API aufsetzen sollten.
- Darf ich Daten meiner App in der Registrierung speichern?
- Die Registrierung eignet sich für Informationen zur Integration mit Windows selbst, etwa Autostart-Registrierung oder Dateizuordnungen, und für sehr kleine Benutzereinstellungen — mehr nicht. Daten über wenige KB hinaus oder listenartige Daten sind bei Backup, Migration und Diagnose gleichermaßen im Nachteil und gehören deshalb in JSON oder SQLite. Zudem wird unter 64-Bit-Windows HKLM\Software, wie es ein 32-Bit-Prozess sieht, nach Wow6432Node umgeleitet, was zu dem Symptom führt, dass „im Registrierungs-Editor der Wert da ist, aber die App ihn nicht lesen kann“. Der richtige Weg ist, die Bitness von Schreib- und Lesezugriff aneinander anzugleichen.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.