【问题标题】:Owners of single entity and owners of group of entities. Constraints or relationship?单个实体的所有者和实体组的所有者。约束还是关系?
【发布时间】:2019-07-01 12:57:21
【问题描述】:

简单的模型。三个简单的主要实体:TaskOwnerTasksGroup

  • 许多所有者可能有许多任务并且相反(多对多)。
  • Task 可以选择性地只属于一个 TasksGroup,但 TasksGroup 可能有许多 Tasks(一对多)

现在对我来说是棘手的部分。 TasksGroup 中每个Task所有者 必须相同。也有可能更改 TasksGroup 内所有 TasksOwners,或者如果没有更改单个 TaskOwners' t 属于任何 TasksGroup

应该有检查约束吗?或者也许应该在 TasksGroupOwners 之间建立“主”关系?如果 Task 属于组,那么 TaskOwners 之间的关系如何?设计这个的好方法是什么?

【问题讨论】:

    标签: database entity-framework database-design domain-driven-design relationship


    【解决方案1】:

    这种模型的一个常见策略是通过要求每个任务属于一个任务组来简化它。这使您可以消除任务和所有者之间的直接关系,但代价是为独立任务使用单个任务任务组。

    【讨论】:

    • 感谢大卫,这是一个有趣的方法。但我担心在我的场景中,区分属于组的任务和独立的任务会变得复杂。可能存在实际“真实”组只有一项任务的情况。
    • 在 EF Core 中,您可以在 (TaskGroupID,OwnerId) 上有一个 FK 任务 -> 任务组,在 (OwnerID) 上有一个单独的任务 -> 所有者。但这有其自身的复杂性。
    【解决方案2】:

    TasksGroup 中每个任务的所有者必须相同

    可以通过首先在TaskOwnerTaskOwner 之间定义一个联结类/表来通过数据库约束强制执行此操作,这在多对多关系中是标准的。

    如果必须将TaskGroup 限制为一个所有者的任务,则连接它的自然位置是任务和所有者信息可用的地方,即联结表。但是,仅通过一个 FK(例如 GroupId)来做到这一点是不够的。 TaskGroup 本身必须被限制为一个所有者。它需要一个 OwnerId 作为其 PK 的一部分,因此 TaskOwner 可以通过 OwnerIdGroupId 引用它。最后,TaskGroups OwnerId 必须是 Owner 的 FK,以确保 TaskOwnerTaskGroup 指的是同一所有者。这相当于这个数据库模型:

    还可以更改 TasksGroup 中所有任务的所有者

    通常,严格的约束和灵活性是相互排斥的。 OwnerId 引用的这个环很难打破。必须更改TaskOwner 中的OwnerId,但这需要四个步骤:

    • GroupId 设置为null
    • 更改OwnerId
    • 更改TaskGroupOwnerId
    • 恢复TaksOwner.GroupId

    对于 SQL 来说已经够复杂了,但对于实体框架来说就更难了,因为 EF 不允许修改主键属性。使用 EF,您必须删除 TaskOwners,创建新的,然后设置 TaskGroup.OwnerIdTaskOwner.GroupId。并非不可能,但非常灵活!

    【讨论】:

    • 这种设计似乎允许每个任务有多个组,每个组有多个所有者。
    【解决方案3】:

    我会选择在TaskGroup 上使用RequiredOwners。事实上,一个甚至可能有不同类型的TaskGroup 实例,其中一种类型要求分配的任务具有相应的所有者,而其他类型可能有不同的规则。但是,这取决于域,因此如果这是您所指的真实域,您会知道:)

    但是,让任务组拥有所有者列表确实是最简单的实现。好吧,TaskGroup 将包含 Id 或代表所有者的值对象列表。一旦配置了任务组,甚至可以从任务组中驱动对任务的所有者分配的管理。

    【讨论】:

      【解决方案4】:

      如果您的 DBMS 支持这些(并且由此产生的性能是可以容忍的),您可以使用简单的模式和一般的数据库约束来做到这一点。不幸的是,大多数人都没有。

      除此之外,最干净的方法是重叠外(超级)键。这是一种不需要 NULL 的伪代码关系方法;省略数据类型和无趣的属性:

      create table Task {
        TaskId,
        key { TaskId }
      };
      
      create table Owner {
        OwnerId,
        key { OwnerId }
      };
      
      create table TaskOwner { /* Bog standard association table */
        TaskId,
        OwnerId,
        key { Taskid, OwnerId }
        reference TO_T { TaskId } references Task { TaskId },
        reference TO_O { OwnerId } references Owner { OwnerId }
      };
      
      create table TaskGroup {
        GroupId,
        OwnerId,
        key { GroupId }, /* Each group has exactly one owner */
        reference TG_O { OwnerId } references Owner { OwnerId }
      };
      
      create table TaskGroupTask {
        TaskId,
        GroupId,
        OwnerId,
        key { TaskId }, /* Each task may belong to at most one group */
        reference TGT_TO { TaskId, OwnerId } references TaskOwner { TaskId, OwnerId },
        /* Foreign superkey coming up */
        reference TGT_TG { GroupId, OwnerId } references TaskGroup { Group, OwnerId }
      };
      

      线索是TaskGroupTask.Ownerid 作为对TaskOwnerTaskGroup 的引用的一部分执行双重职责,从而确保组的所有者与其中每个任务的所有者相同。这里有一个外来的超键——TGT_TG 指的是TaskGroup 键的超集——但如果您的 DBMS 反对这一点,一个可能的解决方法是在超集上创建一个(多余的)唯一索引。

      拥有一个单独的TaskGroupTask 表而不是一个可以为空的TaskOwner.GroupId 属性乍一看似乎有点矫枉过正,但它允许您使用简单的键约束来强制执行任务最多属于一个组的规则.此外,它还避免了 NULL。

      TaskGroupTask中有一个函数依赖{ GroupId } -> { OwnerId }违反了Boyce-Codd范式,但是冗余是由TGT_TGreference控制的,需要强制执行情况规则。

      更改组的所有者(及其所有任务)涉及在同一语句/事务中更新三个表,但适当的 RDBMS 应该没有问题。

      这种设计确实允许一个任务属于多个所有者并且同时在一个组中,创造了一种情况,一个组中的所有任务都属于同一个所有者,但它们属于其他所有者(尽管不是组)也是如此。从你的问题中不清楚这是否是一个问题,所以如果是这样,请告诉我。该设计还不允许没有所有者的组,但我认为这没问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-09-02
        • 1970-01-01
        • 2016-05-26
        • 2017-10-27
        • 2012-09-06
        • 1970-01-01
        • 2016-06-25
        • 1970-01-01
        相关资源
        最近更新 更多