Windows應用程式資料儲存位置怎麼選 ── SQLite / JSON / 登錄檔 / Access 判斷表

· · SQLite, Windows, .NET, C#, 資料儲存, 登錄檔, Access, 設計, 判斷表, 技術諮詢

「設定用INI檔案就好了嗎」「歷史資料越來越多,想改用Access儲存」「登錄檔和設定檔該怎麼分工」。開發Windows業務應用程式時,資料儲存位置的選擇是必經之路。然而這個選擇往往在專案初期隨意決定後就不再檢視,數年後才以「JSON檔案膨脹到數十MB,啟動變慢」「共用資料夾裡的Access檔案每週壞一次」「直接寫入Program Files目錄,導致在Windows 11環境下無法執行」等形式浮現問題。

本文將針對業務用Windows應用程式的本機資料儲存,把「放在哪裡」(資料夾的選擇)與「用什麼儲存」(格式・引擎的選擇)分開整理。以本部落格多次採用的判斷表形式,彙整SQLite / JSON / 登錄檔 / Access各自的優勢與陷阱。

1. 先講結論

  • 儲存位置的選擇是「放在哪裡」與「用什麼儲存」這兩個獨立的判斷。前者選錯會導致權限、多使用者相關的事故,後者選錯則會導致損毀、效能、維護方面的事故。
  • 放置位置的基本原則是:使用者個人的設定與資料放在 %LOCALAPPDATA%Environment.SpecialFolder.LocalApplicationData),全使用者共用的資料放在 %PROGRAMDATA%,而絕不寫入與exe相同的資料夾(Program Files目錄下)1
  • 格式的首選很單純,只有兩種:結構化的小型設定用JSON檔案,會持續增加的業務資料・歷史記錄・需要搜尋的資料用SQLite。這兩者就能涵蓋業務應用程式本機儲存的大部分需求。2
  • 登錄檔是「存放小型旗標或與Windows聯動資訊的場所」,並不是應用程式的資料儲存區。若不了解32bit/64bit的登錄檔重新導向機制(Wow6432Node)就直接使用,會踩到「明明寫入了卻讀不到值」的問題。3
  • 在新開發專案中選擇Access(.accdb)作為資料儲存區的理由幾乎已經不存在。即使是為了與既有資產連動而使用,也會伴隨「ACE提供者的位元數必須一致」這項部署上的限制。4
  • 不論選用哪種格式,機密資訊(密碼、API金鑰)都必須另外處理:不要以純文字方式放進JSON或登錄檔,而要用DPAPI保護。詳情請參閱另一篇文章「Windows應用程式的機密資訊儲存 - 用DPAPI避免純文字設定」。

2. 將要儲存的資料分成4種類

在決定用什麼儲存之前,先為打算儲存的資料性質做分類。業務應用程式的本機資料,大致可分為以下4種。

分類 範例 特徵
設定 連線目標、畫面版面配置、上次開啟的資料夾 小。啟動時全部讀取。有時使用者想直接編輯
業務資料・歷史記錄 測量結果、處理歷史記錄、主檔的本機副本 持續增加。需要搜尋・彙總。損毀時業務影響很大
快取 縮圖、已下載的資源 消失也能重新產生。需要容量管理
機密資訊 儲存的密碼、Token 少量。不可以純文字放置

每種分類適合的存放位置與格式各不相同,這正是本文的核心思路。如果你的應用程式是「設定和歷史記錄全部塞進同一個XML」,重新做這項分類就是改善的第一步。

3. 放在哪裡 ── 資料夾選擇的基本原則

若使用.NET,就以 Environment.GetFolderPath 能取得的位置為基準。5

位置 取得方式 用途
%LOCALAPPDATA%\公司名稱\應用程式名稱 SpecialFolder.LocalApplicationData 使用者個人資料的預設位置。優先考慮這裡
%APPDATA%\公司名稱\應用程式名稱(Roaming) SpecialFolder.ApplicationData 僅限在漫遊使用者設定檔環境中,希望隨使用者移動的設定
%PROGRAMDATA%\公司名稱\應用程式名稱 SpecialFolder.CommonApplicationData 全使用者共用的資料。需要設計ACL
文件資料夾下 SpecialFolder.MyDocuments 僅限使用者會當成自己的檔案處理的產出物(例如匯出的報表)

寫成程式碼就是這麼簡單,但如果連「公司名稱\應用程式名稱」這層目錄結構,以及第一次執行時建立資料夾,都包進共用處理裡,就能防止儲存位置隨意增加的情況。

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);  // 若已存在則不做任何事
        return dir;
    }
}

之所以不用字串串接自行組出 %LOCALAPPDATA%,而是使用 Environment.GetFolderPath,是因為即使在服務帳戶、其他使用者身分執行,或設定了資料夾重新導向的環境下,它都能回傳正確的位置。用工作排程器以不同帳戶執行的瞬間路徑就改變的事故(這是工作排程器文章第5章提到的「手動執行才會動」問題的一種),也能藉此避免。

以下列舉3個陷阱。

  • 不要寫入exe所在的資料夾。 標準使用者無法寫入Program Files目錄下。舊版32bit應用程式可能因為UAC相容性功能而被悄悄重新導向到VirtualStore,這會造成「以系統管理員身分執行和以標準使用者身分執行時,設定檔內容卻不一樣」這種難以理解的症狀。
  • ProgramData是「可以寫入,但並不安全」。 在預設ACL下,可能形成某使用者建立的檔案,其他使用者無法修改的狀態。若要供全使用者共用讀寫,應在安裝程式中建立資料夾並明確設定ACL。
  • 不要把Roaming當成預設選項。 在網域的漫遊使用者設定檔環境中,Roaming目錄下的內容會在登入・登出時同步。若把體積大的資料或機器特有的資料(快取、硬體設定)放在Roaming,會造成同步延遲,或「污染」到其他機器。猶豫不決時就選Local。

4. 用什麼儲存 ── 4種選項的真面目

4.1 JSON檔案 ── 設定的首選

可以用System.Text.Json直接讀寫、人類可讀、方便用Git管理與比對差異——用於設定用途的優點一應俱全。需要注意的地方有兩點。

要做好防止損毀的對策。 若在寫入過程中斷電,殘留下不完整的檔案,下次啟動時就會讀不到。慣用作法是「先寫入暫存檔再置換」,.NET的話File.Replace就提供了附帶備份的置換功能。

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);

讀取端也一開始就內建「若損毀就嘗試.bak,如果也不行就以預設值啟動並提出警告」這種降級處理,設定檔損毀就不會變成支援案件。

不要把它當成資料儲存區使用。 JSON的適用範圍,是「啟動時全部讀取、結束時全部寫入」這個前提能成立的大小(大約數百KB以內)。如果開始把會持續追加的歷史記錄,或需要搜尋記錄的資料放進JSON,那就是該換成SQLite的訊號。

4.2 SQLite ── 持續增加的資料・需要搜尋的資料的首選

SQLite是不需要伺服器、單一檔案、屬於公共領域的內嵌式資料庫,在.NET中可以透過Microsoft維護的ADO.NET提供者Microsoft.Data.Sqlite,或EF Core的SQLite提供者來操作。2 Microsoft本身也將其推薦為Windows應用程式本機資料儲存的手段6,對於「在本機持續增加的結構化資料」,目前的情況是可以優先考慮SQLite。

首先用程式碼示範它有多方便。只要透過NuGet加入Microsoft.Data.Sqlite,不需要伺服器設定,也不需要連線字串的管理畫面,只要指定檔案路徑就能開始使用。

using Microsoft.Data.Sqlite;

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

// 僅第一次執行:啟用WAL模式並建立資料表
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();
}

// 插入時必須使用參數(不要用字串串接組出SQL)
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();
}

可以看到,只要花費和「往JSON檔案裡追加內容」差不多的力氣,就能取得帶索引的搜尋・彙總,以及數量不受限的歷史記錄。若想套用ORM,EF Core的SQLite提供者就是建構在這個函式庫之上。

在此基礎上,列舉幾個實務要點。

  • 啟用WAL模式。 就是上面程式碼中的PRAGMA journal_mode=WAL;。這能提升讀取與寫入的並行性,即使是UI執行緒與背景處理同時操作同一個資料庫的架構,也比較不容易卡住。WAL的設定會永久保存在資料庫檔案本身,所以不需要每次連線都重新發出。
  • 將寫入集中到單一行程的單一路徑。 SQLite的寫入是以資料庫為單位進行互斥。若要從多個執行緒寫入,透過佇列將寫入角色統一為一個,這樣的設計比較安全。此外,大量執行細碎的INSERT時,用明確的交易(transaction)整批處理,速度會比逐筆commit快上好幾個等級。
  • 不要放在網路共用資料夾。 透過SMB的檔案鎖定有很多與環境相關的問題,SQLite官方也將網路檔案系統上的共用列為損毀原因的第一位。7 一旦需要多台機器、多使用者同時使用,那就已經是主從式資料庫(如SQL Server Express)的範疇了。
  • 要知道型別實際上只有4種。 SQLite的實體型別是INTEGER / REAL / TEXT / BLOB,日期和GUID都是以TEXT形式儲存。事先確認一次Microsoft.Data.Sqlite的型別對應規則,日後在日期比較與排序上就不會困擾。8 上面範例中把created_at設為datetime('now')(UTC),也是因為混入本地時間會在排序與夏令時間切換時產生問題。顯示時再轉換成本地時間的做法比較安全。
  • 備份不要用「複製檔案」,而要用VACUUM INTO或Backup API。 單純複製運作中的資料庫檔案,有時會拿到WAL與主體不一致的內容(詳見第6章)。

4.3 登錄檔 ── 僅限小型旗標與Windows聯動資訊

登錄檔適合存放的是「是否已安裝」「開機啟動登錄」「檔案關聯」等與Windows自身聯動的資訊,以及極小型的使用者設定。原則是應用程式自己使用的設定放在HKCU下,機器共通的資訊由安裝程式寫入HKLM(若設計成在執行期間寫入HKLM,會需要系統管理員權限,應避免)。

最大的陷阱在於位元數。在64bit Windows中,從32bit行程看到的HKLM\Software會被重新導向到HKLM\Software\Wow6432Node3 「用登錄檔編輯器看明明有值,應用程式卻讀不到」「32bit應用程式寫入的值,64bit維護工具卻看不到」,這些症狀幾乎都是這個原因。這類問題常在遷移到AnyCPU或轉換為64bit時浮現,請與COM、ActiveX的32bit/64bit問題(「COM/OCX/ActiveX的陷阱 ── Visual Studio的位元數與系統管理員權限」)放在同樣的脈絡下理解。

如果從.NET非得讀取另一種位元檢視不可(例如仍維持32bit的應用程式,需要讀取登錄在64bit側的值),可以用RegistryView明確指定要用哪種檢視。

using Microsoft.Win32;

// 從32bit行程讀取64bit檢視的HKLM
using var hklm64 = RegistryKey.OpenBaseKey(
    RegistryHive.LocalMachine, RegistryView.Registry64);
using var key = hklm64.OpenSubKey(@"SOFTWARE\KomuraSoft\MyApp");
var installDir = key?.GetValue("InstallDir") as string;

反過來說,一旦需要這樣指定,也代表「究竟該寫在32bit還是64bit才對」這個設計判斷被延後了。讓寫入端與讀取端的位元數一致,才是正道。

把超過數KB的資料或陣列型資料放進登錄檔,無論在備份、遷移還是診斷方面都很不利。這類用途應該讓給檔案(JSON / SQLite)。

4.4 Access (.accdb) ── 幾乎不會用於新專案,既有系統整合則要有所取捨

過去業務應用程式的本機資料庫首選是Access(JET/ACE),但現在幾乎沒有理由在新開發專案中選它。主要原因在於部署。若要以程式碼存取.accdb,需要ACE(Access Database Engine)提供者,且應用程式的位元數必須與ACE的位元數一致才能連線。4 還存在與Office位元數共存的問題,「在開發機上能動,但客戶端出現Microsoft.ACE.OLEDB.12.0 提供者未在本機電腦上登錄」是經典的支援案件。此外還需要安裝可轉散發套件(Access Database Engine 2016 Redistributable),這也增加了部署物件。9

儘管如此,實務上仍有需要用到Access的場合,例如與既有的Access業務系統做資料整合、讀取用Access製作的主檔等。此時建議做出以下取捨:

  • 固定讀寫行程的位元數(實務上多半固定為x86較為現實),並在安裝程式中確認對應的ACE是否存在
  • 設計上要避免多人同時寫入放在共用資料夾的.accdb(一旦損毀,復原成本並不划算)
  • 長期而言,要保留遷移到SQLite或伺服器型資料庫的路徑

建議做上述取捨。包含Excel/VBA資產在內的既有資產處理方式,也整理在「VBA是什麼 - 限制、未來性、該替換的場合」一文中。

5. 判斷表

觀點 JSON檔案 SQLite 登錄檔 Access (.accdb)
擅長的資料 小型設定 持續增加的結構化資料、搜尋・彙總 小型旗標、Windows聯動 與既有Access資產整合
資料量參考值 ~數百KB ~數十GB ~數KB ~2GB(規格上限)
搜尋・彙總 ✕(前提是全部讀入) ◎(SQL) ○(SQL)
人類可直接閱讀 △(需工具) △(需Access)
抗損毀能力 △(需自行處理) ○(交易)
多行程同時存取 ○(同一台機器內)
多機共用 ✕(實質上)
額外的部署物件 無(NuGet隨附) 必須有ACE提供者

如同最後一行所示,任何一種本機儲存技術都不適合「多機共用」。放進共用資料夾看起來像是能共用,但JSON沒有互斥機制,SQLite在SMB上的鎖定不可靠,Access則會伴隨損毀風險而遭遇極限。一旦出現多據點、多使用者需要操作同一份資料的需求,請把它當成該建置SQL Server Express之類的伺服器型資料庫,或架設Web API的判斷分界線。

6. 不損毀・可遷移・可還原 ── 不分格式的共通設計

不論選擇哪種格式,只要運用個幾年,就一定會需要以下3項設計。是否在第一次發行時就納入,會大幅影響之後的維護成本。

6.1 為結構・格式附加版本編號

應用程式一旦更新,儲存資料的形式也會跟著改變。「新版本讀取舊版本寫入的資料」的瞬間必定會到來,所以要讓資料本身帶有格式版本資訊。

若是SQLite,PRAGMA user_version正是為這個用途準備的。

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)
        // 用舊版應用程式開啟了新版應用程式建立的資料庫的情況。
        // 不去碰觸不了解的結構,在此中止才安全
        throw new InvalidOperationException(
            $"這個資料庫(版本 {v})是由更新版本的應用程式建立的。");

    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();
}

這是啟動時檢查版本,只套用差異部分,也就是所謂遷移(migration)的最簡形式。開頭之所以拒絕「比自己更新的版本」,是為了防止把應用程式退回舊版本(rollback)時,舊版程式碼寫入自己不了解的結構而造成損毀的事故。JSON的思路也一樣,在根層放入"version": 2,讀取時插入從舊格式轉換的步驟,並拒絕讀取過新的格式。「不要讓沒有版本編號的資料格式問世」,只要守住這一點,未來的自己就能得救。

6.2 事先決定損毀時的降級行為

第4章提到了各格式的損毀對策(JSON的原子寫入、SQLite的交易),但即便如此,仍會遇到「讀不到的資料」——磁碟故障、防毒軟體誤判而隔離、使用者手動編輯。若沒有事先決定應用程式此時該如何應對,就會變成連啟動都做不到的應用程式。

  • 設定讀不到 → 以預設值啟動,並通知使用者此事(若默默套用預設值,會變成「我的設定不見了」的客訴)
  • 業務資料讀不到 → 以唯讀模式或錯誤畫面呈現「哪個檔案損毀了」。不要自動覆寫修復(會讓證據消失)
  • 有備份 → 提議復原。不過自動復原伴隨著「誤判為損毀而回退到舊資料」的風險,原則上要經過使用者操作確認

6.3 備份的重點不在於「有沒有備」,而在於「能不能還原」

本機資料和伺服器上的資料庫不同,沒有人會幫你備份。若要由應用程式端負責處理,就要決定以下3點。

  • 備份什麼:業務資料要備,快取不用備,機密資訊則要考慮到DPAPI的特性——只能在同一使用者、同一台機器上解密(遷移到其他機器需要另外的程序)
  • 何時・備到哪裡:可在啟動時或每日,以世代方式備份到%LOCALAPPDATA%內的backup資料夾。是否再放到共用資料夾或既有的PC備份對象資料夾,則要與營運方協調
  • 怎麼備份:SQLite禁止在運作中直接單純複製檔案。用VACUUM INTO 'backup.db'可以一句話取得具一致性的快照
// VACUUM INTO 不會自動建立父資料夾,且若輸出目的地已存在會發生錯誤。
// 事先完成資料夾建立與不重複檔名的決定
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();

因為世代會持續累積,備份後最好也一併加入「只保留最近N個世代、刪除較舊者」的處理。

而且請務必實際演練一次復原。有備份檔案卻沒人知道怎麼還原、也沒試過,這是業務系統的「常見狀況」。試著寫一份在更換PC時將資料搬到新裝置的操作手冊,通常就能發現備份設計的漏洞(例如用DPAPI保護的認證資訊無法搬移、路徑包含使用者名稱導致換使用者就壞掉等)。關於PC報廢時如何清除資料,也請參閱「報廢Windows PC前該做的事」。

7. 容易猶豫的情境指引

  • 「雖然是設定,但將來似乎會增加」 ── 若預期「啟動時全部讀取」的用法遲早會撐不住,一開始就直接用SQLite。在SQLite裡建一個「settings資料表」並沒有什麼不對。
  • 「從INI/XML遷移」 ── 若只是單純換格式,改用JSON即可;如果這時混雜著歷史記錄類資料,就分離出來改用SQLite。在讀取端保留1~2個版本的舊格式後備方案,遷移會更安全。
  • 「被要求要能用Excel看」 ── 不要把資料儲存區直接改成Excel/Access,而是儲存在SQLite後再加上匯出到CSV/Excel的功能,這樣才能同時滿足資料可靠性與需求。報表輸出的做法請參閱「如何製作Excel報表輸出」。
  • 「想用多個行程讀寫同一份檔案」 ── 若在同一台機器內,SQLite(WAL)相當夠用,但需要設計寫入衝突的處理。若要以檔案為基礎進行整合,請使用「檔案整合與鎖定的最佳實踐」中的互斥模式。
  • 「想在多台機器間共用」 ── 這就代表要從本機儲存畢業了。首選是在相當於檔案伺服器的機器上架設SQL Server Express(免費,資料庫上限10GB)的主從式架構。另外,SQL Server的「LocalDB」儘管名稱如此,實際上是供開發用途的單使用者環境,請不要為了共用目的而選它。若需要跨據點、也要從公司外部使用,就到了該考慮加入Web API架構的階段。

8. 總結

儲存位置的選擇,只要拆成「放在哪裡」(LocalAppData/ProgramData,且不寫入Program Files)與「用什麼儲存」(設定用JSON、持續增加的資料用SQLite、登錄檔盡量減少使用、Access僅限既有系統整合)來思考,絕大多數情況都能毫不猶豫地決定。

在此之上,不論選用哪種格式,都要在第一次發行時就納入第6章的3項套組——格式版本編號、損毀時的降級行為、可還原的備份。唯獨機密資訊,永遠要另外用DPAPI單獨處理。只要掌握本文的判斷表與共通設計,就能大致避開「膨脹到數十MB的JSON」「每週壞一次的共用Access」這類日後代價高昂的架構。若對既有應用程式的儲存方式感到不安,建議先從「盤點什麼資料存在哪裡」開始著手。

相關文章

相關諮詢領域

合同會社小村軟體承接業務應用程式資料儲存方式的檢討(包含從INI/XML/Access遷移的設計),以及資料損毀、效能劣化的原因調查。

參考連結

  1. Microsoft Learn,KNOWNFOLDERID。關於LocalAppData、RoamingAppData、ProgramData等Windows已知資料夾的定義。 

  2. Microsoft Learn,Microsoft.Data.Sqlite overview。關於Microsoft維護的SQLite用ADO.NET提供者概要,以及它是EF Core SQLite提供者的基礎。  2

  3. Microsoft Learn,Registry Redirector。關於在64bit Windows中,32bit行程的登錄檔存取被重新導向到Wow6432Node的機制。  2

  4. Microsoft Learn,Can’t establish a connection to Access Database Engine OLE DB。關於ACE OLE DB提供者必須與存取的行程位元數一致。  2

  5. Microsoft Learn,Environment.SpecialFolder Enum。關於用於從.NET取得已知資料夾的列舉型別。 

  6. Microsoft Learn,Use a SQLite database in a Windows app。官方教學,推薦在Windows應用程式的本機資料儲存中使用SQLite與Microsoft.Data.Sqlite / EF Core。 

  7. SQLite,How To Corrupt An SQLite Database File。關於網路檔案系統上的鎖定失效是資料庫損毀的主要原因。 

  8. Microsoft Learn,Data types (Microsoft.Data.Sqlite)。關於SQLite的4種基本型別,以及DateTime、Guid對應到TEXT的規則。 

  9. Microsoft,Microsoft Access Database Engine 2016 Redistributable。關於用於存取.accdb / .mdb的ACE可轉散發套件(32bit/64bit)。 

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

Windows應用程式的設定檔該存放在哪裡?
如果是使用者個人的設定・資料,基本原則是放在 %LOCALAPPDATA%(Environment.SpecialFolder.LocalApplicationData)下,以「公司名稱\應用程式名稱」的層級結構存放。若是全使用者共用,則放在 %PROGRAMDATA%,但由於預設ACL可能形成其他使用者無法修改的狀態,需要在安裝程式中明確設定ACL。絕對不可以寫入與exe相同的資料夾(Program Files下)。標準使用者無法寫入該處,舊版32bit應用程式還會因為被悄悄重新導向到VirtualStore而產生難以理解的症狀。
設定該用JSON還是SQLite儲存?
結構化的小型設定用JSON檔案,會持續增加的業務資料・歷史記錄・需要搜尋的資料則以SQLite為首選,這兩者就能涵蓋業務應用程式本機儲存的大部分需求。JSON的適用範圍大約是「啟動時全部讀取、結束時全部寫入」這個前提能成立的數百KB以內。如果開始把持續追加的歷史記錄或需要搜尋的資料放進JSON,那就是該遷移到SQLite的訊號。即使是設定,只要預期未來會增加,一開始就在SQLite裡建立settings資料表也沒有問題。
可以把SQLite資料庫放在網路共用資料夾嗎?
應該避免。透過SMB的檔案鎖定有很多與環境相關的問題,SQLite官方也將網路檔案系統上的共用列為損毀原因的第一位。JSON沒有互斥機制,Access也會伴隨損毀風險而遭遇極限,因此任何一種本機儲存技術都不適合多機共用。一旦出現多據點、多使用者需要操作同一份資料的需求,就是該考慮建置SQL Server Express(免費,資料庫上限10GB)之類的伺服器型資料庫,或架設Web API的判斷分界線。
可以把應用程式的資料存進登錄檔嗎?
登錄檔適合存放的僅限於開機啟動登錄、檔案關聯等與Windows自身聯動的資訊,以及極小型的使用者設定。超過數KB的資料或陣列型資料,無論在備份、遷移還是診斷方面都不利,應該讓給JSON或SQLite。此外,在64bit Windows中,從32bit行程看到的 HKLM\Software 會被重新導向到 Wow6432Node,這會造成「用登錄檔編輯器看明明有值,應用程式卻讀不到」的症狀。讓寫入端與讀取端的位元數一致,才是正道。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽