在应用程序中支持自定义字段的设计模式是什么?

我们开发了一个商业应用程序。我们的客户要求定制领域的支持。例如,他们希望向 Customer 表单添加一个字段。

存储字段值和有关字段的元数据的已知设计模式是什么?

我现在看到了这些选择:

选项1 : 将 varchar 类型的 Field1、 Field2、 Field3、 Field4列添加到 Customer 表中。

选项2 : 在 customer 表中添加一个 XML 类型的列,并将自定义字段的值存储在 XML 中。

选项3 : 添加带有 varchar 类型列的 CustomerCustomFieldValue 表,并在该列中存储值。该表还将包含一个 CustomerID,即 CustomFieldID。

CustomerID,  CustomFieldID, Value
10001,       1001,          '02/12/2009 8:00 AM'
10001,       1002,          '18.26'
10002,       1001,          '01/12/2009 8:00 AM'
10002,       1002,          '50.26'

CustomFieldID 是来自另一个名为 CustomField 的表的 ID,具有以下列: CustomFieldID、 FieldName、 FieldValueTypeID。

选项4 : 添加一个 CustomerCustomFieldValue 表,其中包含每种可能的值类型的列,并将值存储在右列中。与 # 3类似,但字段值使用强类型列存储。

CustomerID,  CustomFieldID, DateValue,           StringValue,       NumericValue
10001,       1001,          02/12/2009 8:00 AM,  null,              null
10001,       1002,          null,                null,              18.26
10002,       1001,          01/12/2009 8:00 AM,  null,              null
10002,       1002,          null,                null,              50.26

选项5 : 选项3和4使用特定于单个概念(Customer)的表。我们的客户要求在其他形式的定制领域以及。相反,我们是否应该有一个全系统的自定义字段存储系统?因此,我们不需要使用多个表,如 CustomerCustomFieldValue、 EmployeeCustomFieldValue、 InvoiceCustomFieldValue,而是使用一个名为 CustomFieldValue 的表?虽然在我看来它更优雅,但是这不会导致性能瓶颈吗?

你使用过这些方法中的任何一种吗? 你成功了吗? 你会选择什么方法? 你知道我还应该考虑什么其他方法吗?

另外,我的客户希望自定义字段能够引用其他表中的数据。例如,客户端可能希望向客户端添加“收藏夹支付方法”字段。支付方式在系统的其他地方定义。这就引出了“外键”这个话题。是否应尝试创建约束以确保存储在自定义字段表中的值是有效值?

24102 次浏览

就应用程序代码而言,我不确定。我确实知道自定义字段从数据库中的 EAV 型号中受益匪浅。

根据下面的注释,使用这个模型可能犯的最大错误是将外键放入其中。永远不要在这个模型中加入 FriendID 或 TypeID 之类的东西。将此模型与典型的关系模型结合使用,并在表列中保留外键字段。

第二个重大错误是在这个模型中放置需要与每个元素一起报告的数据。例如,在这个模型中加入像 Username 这样的东西意味着,任何时候你想要访问一个用户,并且需要知道他们的用户名,你最多已经提交了一个连接或者2n 个查询,其中 n 是你正在查看的用户数。当考虑到每个 User 元素通常都需要 Username 属性时,很明显这个属性也应该保留在表列中。

但是,如果您只是在自定义用户字段中使用这个模型,那么就没有问题。我无法想象在许多情况下,用户会输入关系数据,而 EAV 模型对搜索没有太大的不利影响。

最后,不要试图从这里连接数据并得到一个漂亮的记录集。获取原始记录,然后获取实体的记录集。如果你发现自己想要加入这些表格,你可能犯了上面提到的第二个错误。

如果您使用面向对象语言进行开发,我们在这里讨论的是 自适应对象模型自适应对象模型。有相当多的文章介绍了如何用 OO 语言实现它们,但是没有太多关于如何设计数据存储端的信息。

在我工作的公司,我们已经通过使用关系数据库存储 AOM 数据解决了这个问题。我们有一个中心实体表,用于显示域中所有不同的“实体”,如人员、网络设备、公司等。.我们将实际的“表单字段”存储到输入的数据表中,因此我们有一个表用于字符串,一个表用于日期等等。所有数据表都有一个指向实体表的外键。我们还需要表来表示类型端,即某个实体可以具有哪些属性(表单字段) ,这些信息用于解释数据表中的数据。

我们的解决方案的优点是,任何东西都可以在不更改代码的情况下进行建模,包括实体之间的引用、多值等等。还可以将业务规则和验证添加到字段中,并且可以在所有形式中重用它们。缺点是编程模型不太容易理解,而且查询性能会比更典型的 DB 设计更差。对于 AOM 来说,除了关系数据库之外,还有其他一些解决方案可能更好、更容易。

构建一个良好的 AOM 和一个可用的数据存储是一项繁重的工作,如果您没有高技能的开发人员,我不建议您使用它。也许有一天会有一个操作系统解决方案来满足这些需求。

以前曾在标准化组织讨论过定制领域:

类似于选项3的东西是一种方法,我之前已经使用过这种方法。创建一个表来定义其他属性及其相应的值。这将是 Customer 和 CustomerCustomField 表之间的1-N 关系(分别是)。关于定义与自定义属性的关系的第二个问题将是需要考虑的问题。首先想到的是添加一个 DataSource 字段,该字段将包含将属性值绑定到的表。所以本质上你的 CustomerCustomField 应该是这样的:

  1. CustomerId
  2. 财产
  3. 价值
  4. ValueDataSource (可空)

这应该允许您绑定到特定的数据结构,或者仅仅允许您指定未绑定的值。您可以进一步规范化这个模型,但是这样的东西可以工作,并且应该很容易在代码中处理。

如果这些“额外”字段是附带的,并且不想对它们进行搜索,我通常选择选项2(但是喜欢 JSON 胜过 XML)。如果要在自定义字段中进行搜索,选项3并不难做到,通常 SQL 优化器可以从中获得合理的性能。

选择4或5是我的选择。如果你的数据是重要的,我不会去扔掉你的类型信息选项3。(您可能会尝试自己实现完整的类型检查,但这是一项相当大的工作,数据库引擎已经为您完成了这项工作。)

一些想法:

  • 确保你的 CustomFields有一个 DataType列。
    • CustomFieldValues上使用基于 UDF 的检查约束来确保由 CustomFields.DataType指定的列是非空的。
    • 您还需要一个标准的检查约束来确保只有一个非空值。
  • 关于外键,我将把它们建模为一个单独的 DataType
    • 每个潜在的交叉表引用都需要自己的列,这很好,因为它维护了参照完整性。
    • 无论如何,您必须在应用程序代码中支持这些关系,因此它们在数据库中被硬编码的事实实际上并不限制功能。
    • 如果您正在使用 ORM,那么它也可以很好地与 ORM 一起工作。
  • 对于选项5,使用中间表对关系进行建模。
    • 您仍然会有一个 CustomerCustomFieldValue,但是只有 CustomerIDCustomFieldValueID列。
  • 每一步都要仔细考虑你的约束条件。这是个棘手的问题,一个失误就可能导致彻底的灾难。

我在一个当前正在开发的应用程序中使用它。目前还没有出现任何问题,但是 EAV 的设计还是把我吓得魂飞魄散。小心点。

顺便说一句,XML 可能也是一个不错的选择。从直接经验来看,我对它的了解不多,但它是我在开始数据设计时考虑的选项之一,而且看起来很有希望。

我同意下面的海报,选项3,4,或5最有可能是合适的。但是,您建议的每个实现都有其好处和成本。我建议你选择一个符合你具体要求的。例如:

  1. 选项1优点: 快速实现。允许数据库操作在自定义字段(搜索,排序。)
    选项1的缺点: 自定义字段是通用的,所以不需要强类型字段。数据库表效率低下,在大小方面存在许多永远不会使用的无关字段。需要预计允许的自定义字段数。
  2. 选项2优点: 快速实现。灵活,允许任意数量和类型的自定义字段。
    选项2的缺点: 不能在自定义字段上执行 DB 操作。如果您以后只需要显示自定义字段,或者仅在每个客户的基础上对数据进行少量操作,那么这样做是最好的。
  3. 备选方案3的优点: 既灵活又高效。DB 操作可以被执行,但是数据被标准化以减少浪费的空间。我同意未知(谷歌)的建议,你增加一个额外的列,可以用来指定类型或源信息。 选项3缺点: 稍微增加了开发时间和查询的复杂性,但是这里真的没有太多缺点。
  4. 选项4与选项3相同,只是类型化数据可以在 DB 级别上进行操作。在选项3中将类型信息添加到链接表中,可以在应用程序级别执行更多操作,但是 DB 不能进行比较或排序。3和4之间的选择取决于这个要求。
  5. 选项5与3或4相同,但更灵活地将解决方案应用于许多不同的表。在这种情况下,代价将是这个表的大小将会变得更大。如果您正在执行许多代价高昂的联接操作以获得自定义字段,则此解决方案可能无法很好地伸缩。

附言。如下所述,术语“设计模式”通常指的是面向对象程序设计。您正在寻找数据库设计问题的解决方案,这意味着大多数关于设计模式的建议都不适用。

我目前正在处理一个有同样问题的项目,我选择使用选项3,但是我添加了一个 FieldType 字段和一个 ListSource 字段,以防 FieldType = “ list”。ListSource 字段可以是一个查询、一个 sql 视图、一个函数名,或者可以产生列表选项列表的内容。在我的情况下,试图存储这样的字段的最大问题是这个字段列表可能会更改,并且允许用户稍后编辑数据。因此,如果字段列表发生更改并进行编辑,该怎么办。对于这种情况,我的解决方案是只有在列表没有更改时才允许编辑,如果更改了,则显示只读数据。

表没有关系,想象一个 表。这些字段可以转换为一个窗体。显然,这个表单是静态的,因为字段在模型中是固定的。

CCK 所做的(它是一个旧的 Drupal6模块)是创建其他将 额外的字段与一个或多个表关联起来的表。为此,创建了3个表,称为:

  • 桌子
  • 属性
  • 价值观

如果不想触及当前模型,可以在它们上面加上前缀。

注意 : 反模式称为 实体-属性-价值模型(EAV)。由于获取相关记录的延迟,不建议在大型数据库中使用。或者,如果是这种情况,至少在不需要频繁查询且这些表在您的模型中不重要的表上使用。

桌子中有一个表名列表,您将允许其中包含额外的字段和一个 身份证。这里只有 idname(这是要展开的表的名称)。如果你想给一个表定义新字段的可能性,你只需要在这里添加一条记录。

属性字段中,创建了称为 idnametypetables_id的字段。name是将在新版本表中关联的字段名。并且在 type中与它使用的值类型(整数、字符串、日期、时间等)相关联。数据类型依赖于数据库引擎,但是您只能从引擎允许的一些数据类型开始。只有这些字段没有关系或索引,因为它们是可选的。table_idname0(table.id)中使用的记录的 id相关联。

价值观中,字段使用您为属性定义的所有可能类型编写。例如,如果只允许整数和字符串,则只需创建两个字段,一个用于整数,一个用于字符串。需要注意的是,这些类型的默认值始终是 null。与其他两个字段一起,我们称之为 attributes_id,它与 属性记录相关联,该记录将指示必须用于存储值的字段的名称和类型(即,根据属性表定义的该表的字段)。还有一个叫做 objective_id,它指向期末表中的记录(比如说 中的记录)。

各就各位

身份证 姓名 拉特 很长
1 阿尔托临终关怀医院 -20.290467 -70.100192
2 政府 -22.093082 -70.201210
3 El Cobre 医院 -22.450496 -68.908442

桌子

身份证 姓名
10
20 桌子

属性

身份证 姓名 类型 Tables _ id
100 钥匙 整数 10
200 电子邮件 绳子 20
300 开始 约会 10

目标身份 属性 _ id 整数 约会 绳子
1 100 117 无效 无效
1 300 无效 2022-01-01 无效
3 100 217 无效 无效

现在,在表单视图中,可以根据 属性表中是否存在与该表相关联的记录来检查是否添加新表单字段(在这个视图中,您知道最后一个表是 ,它正在对与该表名相关联的所有记录执行 SELECT)。根据其类型,可以添加验证。当您存储或保存这个字段时,您将在 价值观表中创建或更新它,并将表单提供的值写入适当的字段中,最终表(工作站)中记录的 id 将被复制到 objective_id。要实现动态,必须将此生成集成到与表关联的所有窗体中。或者至少在那些您以前在 桌子中定义的。

在报表中显示值的相同分析。如果您的报告(站点)有属性,您可以在 属性表中迭代,并且对于获得的每个记录,您都可以查询通过 attributes_idobjective_id(这是要报告的当前站点的 id)进行的值表过滤。

车站报告表(视图)

姓名 拉特 很长 钥匙 开始
阿尔托临终关怀医院 -20.290467 -70.100192 117 2022-01-01
政府 -22.093082 -70.201210
El Cobre 医院 -22.450496 -68.908442 217

可以直接删除 价值观表中的值,但不能从 桌子属性表中删除记录。这是为了避免将来的不一致。为了简化后者,可以通过添加日期属性(如 deleted_at)来选择逻辑删除。

若要防止不一致,必须创建一个唯一约束,以防止关联的重复记录。

  • Table : name
  • 属性 : (name,tables _ id)
  • Value : (Objective _ id,properties _ id)

要添加自定义验证,您可以在 属性表中添加一个带有管理员可以配置的正则表达式的字段。根据数据类型,它们应该包含默认值。这是可选的。

属性

身份证 姓名 类型 Tables _ id 正则表达式
100 钥匙 整数 10 ^\d+$
200 电子邮件 绳子 20 [^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+
300 开始 约会 10 ^\d{4}-([0]\d|1[0-2])-([0-2]\d|3[01])$

显然,这适用于许多用途,并促进了用户对模型的增长。与之相对的是缺乏可以简化查询的关系。

检查 这些考虑因素