【问题标题】:Modelling group membership with a "selected" member in Database使用数据库中的“选定”成员对组成员身份进行建模
【发布时间】:2011-12-18 07:05:10
【问题描述】:

在我的数据模型中,我有一个实体 Group 和另一个实体 GroupMember。一个Group 包含一个或多个GroupMembers,但一个GroupMember 只能同时包含一个Group。到目前为止没问题,在数据库中GroupMember 有一个指向Group id 的外键。但是现在我想让其中一个成员成为“默认”或“选定”成员。应该始终只有一个选定的成员,不多也不少。

我尝试在 Entity Framework 中对此进行建模,它具有一个 1-* 关联来建模组成员资格和一个 (0..1)-1 关系来保存Group 内选定的GroupMember 的一个实例。

但是现在我显然遇到了问题,当插入 GroupGroupMember 的实例时,我收到一个错误,即实体框架无法确定插入项目的顺序,因为 Group 需要有效的 GroupMember作为默认成员,但除非不引用现有的 Group 实体,否则无法插入 GroupMember。可以说是鸡蛋问题...

最简单的方法可能是选择建立其中一个关系,但这会消除我在正常数据库操作期间希望拥有的约束。理想情况下,实体框架应以任意顺序将数据插入数据库,并且数据库应仅在事务结束时检查约束违规。

另一种方法是将所选成员建模为 GroupMember 中的布尔属性“IsSelected”。但是我不确定如何仅使用实体框架设计器来确保同时只有一个选定的成员(我想尽量避免直接使用数据库)。

您能提供任何指导吗?处理此问题的首选方法是什么?谢谢!

【问题讨论】:

    标签: c# .net database entity-framework database-design


    【解决方案1】:

    正确的建模方法是使用association table

    +-------+              +--------+                 +--------+
    | Group |--------------| Member |-----------------| Person |
    +-------+ 1          * +--------+ 1             1 +--------+
        | 1                                               | 1
        |                                                 |
        |                                                 |
        | 0..1                                            |
    +--------+                                            |
    | Leader |--------------------------------------------+
    +--------+ 0..1
    

    我假装“领导者”是对团队中“特殊”人员的准确描述。您应该尝试使用比“selected”更具描述性的名称。

    架构如下所示:

    CREATE TABLE Group
    (
        Id int NOT NULL PRIMARY KEY,
        ...
    )
    
    CREATE TABLE Person
    (
        Id int NOT NULL PRIMARY KEY,
        ...
    )
    
    CREATE TABLE Member
    (
        PersonId int NOT NULL PRIMARY KEY
            CONSTRAINT FK_Member_Person FOREIGN KEY REFERENCES Person (Id)
                ON UPDATE CASCADE ON DELETE CASCADE,
        GroupId int NOT NULL
            CONSTRAINT FK_Member_Group FOREIGN KEY REFERENCES Group (Id)
                ON UPDATE CASCADE ON DELETE CASCADE
    )
    CREATE INDEX IX_Member_Group ON Member (GroupId)
    
    CREATE TABLE Leader
    (
        PersonId int NOT NULL PRIMARY KEY
            CONSTRAINT FK_Leader_Person FOREIGN KEY REFERENCES Person (Id)
                ON UPDATE CASCADE ON DELETE CASCADE,
        GroupId int NOT NULL
            CONSTRAINT FK_Leader_Group FOREIGN KEY REFERENCES Group (Id)
                ON UPDATE CASCADE ON DELETE CASCADE,
        CONSTRAINT U_Member_Group UNIQUE (GroupId)
    )
    

    它表达了有关关系的以下信息:

    • 存在一个组,句号。它可能有也可能没有成员。如果它没有成员,那么根据定义它也没有领导者。它仍然存在,因为以后可能会添加新成员。

    • 一个人存在,句号。一个人不会仅仅因为他/她的群体不复存在。

    • 一个人可能是一个且只有一个组的成员

    • 一个人也可能是一个组的领导。一个组一次只能有一个领导者。小组的领导者可能或可能不被视为成员

    您可能认为这种关系设计施加的约束比您在问题中提出的约束要宽松得多。你是对的。这是因为您的问题是将数据模型与业务/领域要求混为一谈。

    除了这个模型之外,您还应该有几个业务规则,由您的应用程序强制执行,例如:

    • 如果组没有成员,则将其删除/停用/隐藏。

    • 如果已停用/隐藏的群组获得成员,则会重新激活/显示。

    • 一个人必须是某个组的成员。添加新人时必须提供此信息(不必是现有组,可以是新组)。如果一个人的会员组被删除,这应该触发一个例外过程;或者,如果组仍有成员,则不允许删除该组。

    • 有成员的组必须有领导。如果将新人添加到空组中,则该人将成为领导。如果leader(人)被删除,那么应该根据一些标准自动选择一个新的leader,或者触发一个异常流程。

    为什么这是“正确”的设计?

    首先是因为它准确地描绘了实体及其关系的独立性。群体和个人并不实际上相互依赖;只是您的业务规则规定您对没有组成员资格的人或没有任何成员或领导的组不感兴趣。

    更重要的是因为索引和约束更加清晰:

    • 查询组的成员速度很快。
    • 查询一个人的成员资格很快。
    • 查询组长速度很快。
    • 查询同时是领导的人很快。
    • 删除群组会自动移除所有群组成员/领导者。
    • 删除人员将自动删除所有组成员/领导。
    • 更改成员资格仍然是一个 UPDATE 语句。
    • 更换领导层仍然是一个 UPDATE 声明。
    • SQL Server 不会抱怨多个级联路径。
    • 每个表最多有 2 个索引,位于您希望被索引的列上。
    • 您可以轻松扩展此设计,即适应不同类型的会员资格。
    • 成员资格/领导职位的更改绝不会干扰简单的查询(例如按姓名查找人员)。
    • 每个 ORM 都可以毫无问题地处理这个问题。通常,您会将其视为多对多,但您也许可以将其实现为可空的一对一。

    所有其他解决方案都有一些严重的致命缺陷:

    • GroupId 放在Person 上,将LeaderId 放在Group 上会导致无法解决的循环,除非使至少一个列可以为空。您也将无法CASCADE 其中一个关系。

    • GroupId 放在Person 上并在Person 上附加一个IsLeader 不允许您在没有触发器的情况下强制执行上限(每个组1 个领导者)。实际上,从技术上讲,您可以使用过滤索引(仅限 SQL '08),但它仍然是错误的,因为 IsLeader 位实际上并未指定关系,并且如果您不小心更新了 GroupId 但忘记了 IsLeader那么你突然让这个人成为了一个完全不同的团队的领导者,并且可能违反了至多一个约束。

    • 有些人会选择将GroupId添加到Person,但仍然维护Leader关联表。从概念上讲,这是一个更好的设计,但由于您可能有一个从 Group 到 Person 的 CASCADE,因此您也无法在 Leader 上放置一个双向 CASCADE(您将获得“多个级联路径”错误,如果你尝试)。

    是的,我知道这需要更多的工作,并且需要您更加努力地思考您的业务规则是什么,但相信我,这就是您想要做的。其他任何事情都只会导致痛苦。

    【讨论】:

    • 我所说的域实际上并不是关于作为群体成员的人。更准确地说,用户看到了一个选项列表,并且必须选择其中一个选项。这些选项仅在其所在组的上下文中才有意义,它们本身并不存在。
    • @aKzenT:那么,您需要更具体地说明您的要求。数据建模就是对现实世界的关系进行建模。如果您不告诉我们现实世界的关系是什么(或至少给出一个相当接近的类比),那么我们只能根据您确实提供的信息来回答。无论如何,即使这些“选项”仅在其组的上下文中“有意义”,如果您需要另一个辅助关系,这很可能是比简单的 1:M 关系更好的建模方式它。
    • 领导表的基本思想仍然可以应用,这是一个好主意。但是我不确定是否有一种很好的方法可以在实体框架中对此进行建模。
    • @aKzenT:“好方法”是什么意思?多对多关系非常普遍。如果关系本身没有任何附加属性,则 EF 可以像 1:M 一样生成代码。如果它确实具有属性,那么它仍然很容易在代码中组装。
    • 好吧,我试着描述一下我的实际模型:我有一个检查重复记录的列表。此检查的结果是包含应该合并的记录的多个组,因为它们可能代表相同的现实世界实体。组中的条目(成员)包含对原始记录的引用,但更重要的是元信息,例如记录在该组中的概率。用户在每个组中选择应该用作以后合并记录的基础的成员。
    【解决方案2】:

    最简单的方法如下:

    • GroupMember 实体上声明布尔属性 IsSelected
    • GroupMember 类添加分部类声明(所有 EF 实体类都声明为分部,因此很容易使用自定义代码对其进行扩展)。
    • 订阅IsSelected 属性的“BeforeValueChanging”事件(我不记得事件的确切名称,但您可以确定 EF 提供了类似的东西。)。
    • 在您的事件处理程序中,您可以实现所需的逻辑。无需直接关心数据库...

    HTH!

    【讨论】:

      猜你喜欢
      • 2011-06-10
      • 2011-10-20
      • 1970-01-01
      • 1970-01-01
      • 2021-01-14
      • 1970-01-01
      • 2011-12-29
      • 2019-07-05
      • 1970-01-01
      相关资源
      最近更新 更多