用“选定的”成员来建模组成员资格。数据库中的成员

发布于 2024-12-12 21:28:34 字数 888 浏览 0 评论 0原文

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

我尝试在实体框架中对此进行建模,其中具有一个 1-* 关联来建模组成员身份,以及一个 (0..1)-1 关系来保存 Group 内所选 GroupMember 的实例

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

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

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

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

In my data model I have an entity Group and another entity GroupMember. One Group consists of one or more GroupMembers, but one GroupMember can only be in one Group at the same time. So far no problem, in the database GroupMember has a foreign key to the Group's id. However now I want to have one of the members to be the "default" or "selected" member. There should always be exactly one selected member not more and not less.

I tried modelling this in Entity Framework having one 1-* association to model the group membership and one (0..1)-1 relationship to save an instance of the selected GroupMember inside of Group.

However now I obviously have the problem, that when inserting instances of Group and GroupMember I get an error that entity framework cannot determine in which order to insert the items, since Group requires a valid GroupMember as the default member, but the GroupMember cannot be inserted unless without referencing an existing Group entity. A chicken-egg problem so to say...

The easiest way would probably be to make one of the relationships optionally, but this would remove a constraint that I would like to have during normal database operation. Ideally entity framework should insert the data in any order to the database and the database should check constraint violations only at the end of the transaction.

Another way would be to model the selected member as a boolean property "IsSelected" in the GroupMember. However I'm not sure how to ensure that there is only one selected member at the same time using only the entity framework designer (I want to try to avoid working with the database directly).

Can you offer any guidance what would be the preferred way to handle this? Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

无悔心 2024-12-19 21:28:34

对此进行建模的正确方法是使用关联表

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

I'我假装“领导者”是对群体中“特殊”人的准确描述。您应该尝试使用比“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)
)

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

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

  • 一个人存在,就这样。一个人不会仅仅因为他/她的群体不存在而不再存在。

  • 一个人可以是一个且仅有一个群体的成员

  • 一个人也可能是一个团体的领导者。一个团体一次只能有一名领导者。群组的领导者可能也可能不被视为成员

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

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

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

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

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

  • 一个团体有成员就必须有领导者。如果将新人添加到空组中,则该人将成为领导者。如果领导者(人)被删除,则应根据某些标准自动选择新的领导者,否则应触发例外流程。

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

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

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

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

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

  • GroupId放在PersonLeaderId上on Group 会导致循环,除非使至少一列可为空,否则无法解决该循环。您也无法CASCADE其中一种关系。

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

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

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

The correct way to model this is with an association table:

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

I'm pretending that "leader" is an accurate description of who is "special" in the group. You should try to use a more descriptive name than "selected".

The schema looks like this:

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

It expresses the following information about the relationships:

  • A group exists, period. It may or may not have members. If it has no members, then by definition it also has no leader. It still exists, because new members might be added later.

  • A person exists, period. A person would not cease to exist simply because his/her group does.

  • A person may be a member of one and only one group.

  • A person may also be the leader of a group. A group can only have one leader at a time. The leader of a group may or may not be considered a member.

You may think that the constraints imposed by this relational design are significantly looser than the ones asked about in your question. And you'd be right. That's because your question is conflating the data model with the business/domain requirements.

In addition to this model you should also have several business rules, enforced by your application, such as:

  • If a group has no members, it is deleted/deactivated/hidden.

  • If a deactivated/hidden group acquires members, it is reactivated/shown.

  • A person must be a member of some group. This information must be supplied when a new person is added (it does not have to be an existing group, it can be a new group). If a person's membership group is deleted, this should trigger an exception process; alternatively, do not allow a group to be deleted if it still has members.

  • A group which has members must have a leader. If a new person is added to an empty group, that person becomes the leader. If the leader (person) is deleted, then a new leader should be automatically selected based on some criteria, or an exception process should be triggered.

Why is this the "correct" design?

First of all because it accurately portrays the independence of entities and their relationships. Groups and persons do not actually depend on each other; it is simply your business rules dictating that you are not interested in persons without a group membership or groups without any members or leaders.

More importantly because the indexing and constraints are far cleaner:

  • Querying the members of a group is fast.
  • Querying the membership(s) of a person is fast.
  • Querying the leader of a group is fast.
  • Querying the persons who are also leaders is fast.
  • Deleting a group will automatically remove all group memberships/leaders.
  • Deleting a person will automatically remove all group memberships/leaderships.
  • Changing a membership is still a single UPDATE statement.
  • Changing a leadership is still a single UPDATE statement.
  • SQL Server won't complain about multiple cascade paths.
  • Each table has at most 2 indexes, on the columns you'd expect to be indexed.
  • You can easily extend this design, i.e. to accommodate different types of membership.
  • Changes to membership/leadership will never interfere with simple queries (such as finding a person by name).
  • Every ORM can handle this with no trouble at all. Generally you would treat it as a many-to-many but you might be able to implement it as nullable-one-to-one.

All of the other solutions have some serious, fatal flaw:

  • Putting the GroupId on Person and LeaderId on Group results in a cycle that cannot be resolved except by making at least one of the columns nullable. You will also not be able to CASCADE one of the relationships.

  • Putting the GroupId on Person and an additional IsLeader on Person does not allow you to enforce the upper bound (1 leader per group) without a trigger. Actually, you technically can with a filtered index (SQL '08 only), but it's still wrong-headed because the IsLeader bit does not actually designate a relationship, and if you accidentally update the GroupId but forget about IsLeader then you've suddenly just made this person the leader of an entirely different group, and probably violated the at-most-one constraint.

  • Some people will choose to add GroupId to Person but still maintain the Leader association table. That is a better design conceptually, but since you'll likely have a CASCADE from Group to Person, you won't be able to put a two-way CASCADE on Leader as well (you'll get the "multiple cascade paths" error if you try).

Yes, I know it's a little more work and requires you to think a little harder about what your business rules are, but trust me, this is what you want to do. Anything else will only lead to pain.

荒路情人 2024-12-19 21:28:34

最简单的方法如下:

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

HTH!

The easiest way to do this is as follows:

  • Declare a boolean property IsSelected on the GroupMember entity.
  • Add a partial class declaration to the GroupMember class (all EF entity classes are declared partial, so it's easy to extend them with custom code).
  • Subscribe to the 'BeforeValueChanging' event of the IsSelected property (I can't remember the exact name of the event from the top of my head, but you can be sure that EF provides something like that.).
  • In your event handler, you then can implement the desired logic. There's no need to directly care about the database...

HTH!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文