【问题标题】:How to make a composite primary key bidirectional?如何使复合主键双向?
【发布时间】:2021-08-31 12:35:57
【问题描述】:

我的问题出在标题上。我想将复合主键设为双向。我说的双向是什么意思?让我解释一下:

我有一张友谊表。主键是:SenderId 和 ReceiverId

在 OnModelCreating 中:

builder.Entity<Friendship>()
                .HasKey(i => new { i.SenderId, i.ReceiverId });

所以我的友谊实体有像 {SenderId, ReceiverId} 这样的主键。但不像 {ReceiverId, SenderId}。我想让钥匙成为双向的。这样,我的友谊请求将是唯一的,并且我不会创建重复的友谊请求,如下所示:

这可能吗?

注意:我知道如何检查是否存在具有这些 ID 的条目。但我想实现数据库以两种方式拒绝具有相同 ID 的新条目。

【问题讨论】:

  • 在您的情况下,您不需要第二次重复按键。一对就足够了
  • @Serge 这是 OP 想要在数据库级别阻止的,即 不允许 使用 let say (1, 2) 和 (2, 1) 键的记录跨度>

标签: c# asp.net sql-server entity-framework-core .net-5


【解决方案1】:

您需要一个符合canonical form 的密钥,即始终以相同的顺序出现。

例如,除了ReceiverIDSenderID,您将拥有两个中性命名的字段(例如ParticipantAParticipantB)加上一个ParticipantA 必须小于@987654328 的约束@。通过这种方式,您可以设置一个约束,使每对 ID 只能出现一次,而不管谁在发送和接收。

然后您需要添加一个指定关系方向的列,例如谁是发件人,或者是否是双向的。

因此而不是

SenderID   ReceiverID
--------   ----------
1111       2222
4444       3333
1234       5678
5678       1234

你应该有

ParticipantA   ParticipantB  Sender
------------   ------------  ---------
1111           2222          A
3333           4444          B
1234           5678          BIDIRECTIONAL

【讨论】:

  • 感谢您的回答。我想要一个在输入重复条目时引发错误的数据库控件,而不是拥有一个控制它是否重复的新列。
  • 你误会了。没有控制它是否重复的列。有一个约束可以防止重复。因为不再允许重复,所以您需要一列来告诉您方向性。
  • 哦,好吧,你能告诉我如何使用 fluent API 编写该约束吗? @JohnWu
  • @Huntow Apparently not纯流利,你需要编写SQL ALTER TABLE Friendship ADD CONSTRAINT Friendship_Id_Order CHECK (ParticipantA &lt; ParticipantB)
【解决方案2】:

正如@John Wu 指出的那样,您需要一个复合键,其中包含的键值始终按顺序排列,因此 (1, 2) 和 (2, 1) 都映射到 (1, 2),因此被视为相等。

但是,建议的实现需要对数据库模型以及使用它的方式进行太多更改。如果需要获取User 的此类信息,还可以防止具有自然的User.SendersUser.Receivers 集合导航属性。

因此,您不需要修改现有的主数据模型,而是需要对“规范化”复合键 (User1Id, User2Id) 的唯一约束(索引),其中(在 preudo 代码中)User1Id = Min(SenderId, ReceiverId)User2Id = Max(SenderId, ReceiverId)

这是特定于数据库的实现。对于 SqlServer,它可以通过创建两个计算列然后在它们上创建唯一约束(索引)来实现,例如


// the two shadow properties mapped to the computed columns
modelBuilder.Entity<Friendship>()
    .Property<Guid>("UserId1")
    .HasComputedColumnSql("case when SenderId < ReceiverId then SenderId else ReceiverId end");

modelBuilder.Entity<Friendship>()
    .Property<Guid>("UserId2")
    .HasComputedColumnSql("case when SenderId < ReceiverId then ReceiverId else SenderId end");

// the unique index using them
modelBuilder.Entity<Friendship>()
    .HasIndex("UserId1", "UserId2")
    .IsUnique();

【讨论】:

  • 不错的方法!只有一个补充:我会让计算列持久化(存储 = true)。
  • 嗨@Gert,因为计算值无论如何都必须存储在索引中,这不是多余的吗?我不确定,只是问问。
  • 取决于列的使用方式。如果它是 just 的唯一索引,它可能是多余的。实际上,这是一个广泛的主题,但我喜欢持久化计算列,让 Sql Server 有机会跳过重新评估,但它并不总是这样做。
猜你喜欢
  • 1970-01-01
  • 2019-06-19
  • 2017-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-16
  • 1970-01-01
  • 2018-07-29
相关资源
最近更新 更多