【问题标题】:Is it okay to duplicate database data in the application code?可以在应用程序代码中复制数据库数据吗?
【发布时间】:2025-12-04 07:10:02
【问题描述】:

我正在使用 sqlite 数据库将结果存储在嵌入式 C++ 应用程序中。

我有几个单列表,我称之为“域”表,其他表中的列将它们作为外键引用。这些本质上是枚举类型的表,仅在初始化时更改一次。比如一张存储状态数据类型的表:

CREATE TABLE status_domain (status TEXT PRIMARY KEY NOT NULL UNIQUE);
INSERT INTO status_domain VALUES ('pending');
INSERT INTO status_domain VALUES ('in_progress');
INSERT INTO status_domain VALUES ('error');
INSERT INTO status_domain VALUES ('complete');  
.
.
CREATE TABLE my_other_table (
    .
    .
    status  TEXT NOT NULL,     
    .
    .
    FOREIGN KEY (status) REFERENCES status_domain(status)
);

域表的目的是利用sqlite的外键约束(参照完整性)。

写入这些表的 C++ 代码不知道架构。我想知道在 C++ 中复制这些表是否是糟糕的设计。例如:

enum StatusEnum { pending, in_progress, error, complete };

我看到四个选项:

  1. 在不知道我插入的状态值是否有效的情况下插入my_other_table。如果状态值无效,这将在运行时失败。
  2. 使用 C++ 枚举复制 status_domain,以便编译器不会让我执行无效状态的插入。这违反了 DRY 原则,因为如果架构发生更改,我将不得不在这两个地方进行更改。
  3. 废弃status_domain 表并让C++ 枚举强制执行有效的数据类型。 C++ 代码将是插入这些表的唯一位置,因此这似乎是合理的。但是,在模式中明确声明状态类型是很好的。
  4. 使 sqlite 包装器代码更能识别数据库/模式。我认为这不值得。

我倾向于选项 2,但犹豫不决,因为它存储的东西可能会在两个不同的地方发生变化。

注意:还有一些类似的(更长的)表格我没有分享。

【问题讨论】:

    标签: c++ database sqlite


    【解决方案1】:

    您说的是维护数据库模式和过程语言代码之间的一致性的众所周知的问题。这个问题没有很好的解决方案。有几种方法,例如 Microsoft 的 EntityFramework。没有一个是完美的。

    我建议您考虑以下解决方案:

    1. 编写一段 C++ 代码,从枚举中生成 SQL 查询(C++ 中没有反射,但有一个选项可以检查所有枚举成员是否存在于 switch 语句中) .更改枚举后,您应该重新创建应该重建表并运行此查询的 SQL 查询。

    2. 编写 SQL 查询,该查询将生成一段 C++ 代码,其中枚举来自表的新状态。更改表后,您必须重新运行查询,然后重新编译 C++ 代码。

    这些程序不是全自动的,但至少它们给出了一些可以遵循的政策。

    【讨论】:

      【解决方案2】:

      根据您所展示的内容,我倾向于选项 3(废品 status_domain)。作为参考,我看不到该表真正为您提供了什么(my_other_table 中没有您在连接或其他操作中需要的相关数据)。

      my_other_table 你总是可以这样做:

      status TEXT NOT NULL CHECK (status IN ('pending', 'in_progress', ...))
      

      实际上也没有必要为此使用字符串。 C++ enum 值应该可以正常工作,也可以通过CHECK 约束进行检查。

      【讨论】:

      • 我猜 CHECK 比拥有额外的表要好,但它仍然没有解决重复问题,这是我可能不得不忍受的问题。
      【解决方案3】:

      那么你似乎想要选择选项 2。我有点假设原因在于您无法共享的表格。在这种情况下,我会选择GNU autogen 并执行以下操作:

      status.def

      自动生成定义状态; 状态 = { 数字 =“1”;名称="待定"; }; 状态 = { 数 =“2”; name="in_progress"; }; 状态 = { 数字 =“3”;名称="错误"; }; 状态 = { 数字 =“4”;名称=“完成”; };

      gen.tpl

      [+ autogen5 模板 sql=%s.sql h=%s.h (setenv "SHELL" "/bin/sh") +][+ CASE (后缀) +][+ == sql +] CREATE TABLE status_domain (id INTEGER PRIMARY KEY, status TEXT NOT NULL UNIQUE); [+ FOR status "\n" +]INSERT INTO status_domain VALUES ([+num+], '[+name+]');[+ ENDFOR+] 创建表 my_other_table ( . . status_domain_id 整数, . . 外键 (status_domain_id) 参考 status_domain(id) ); [+ == h +]枚举StatusDomainEnum { [+ FOR 状态 ",\n" +] [+name+] = [+num+][+ENDFOR+] }; [+ESAC+]

      哪些输出:

      status.h

      枚举状态域枚举 { 待定 = 1, in_progress = 2, 错误 = 3, 完成 = 4 };

      status.sql

      CREATE TABLE status_domain (id INTEGER PRIMARY KEY, status TEXT NOT NULL UNIQUE); INSERT INTO status_domain VALUES (1, 'pending'); INSERT INTO status_domain VALUES (2, 'in_progress'); INSERT INTO status_domain VALUES (3, 'error'); INSERT INTO status_domain VALUES (4, 'complete'); 创建表 my_other_table ( . . status_domain_id 整数, . . 外键 (status_domain_id) 参考 status_domain(id) );

      【讨论】: