我有一个Post
实体和一个FbPost
实体。
Post.FbPost
为null或FbPost
,并且没有两个Post
实体可以引用相同的FbPost
实体。换句话说,zero-or-one to one
。
在SQL Server(理想情况下)中以zero-or-one to one
而不是many to one
强制执行此操作的正确方法是什么?
如果不可能,我该如何在EF级别强制执行此操作?
答案 0 :(得分:5)
通常情况下,您可能会尝试将外键(在源表中)置为可空并对其设置唯一约束。
它可以为空的事实意味着您可以在源表中有一个空条目,该条目不引用目标表中的条目。并且,如果它不为null,则唯一约束确保源表中只有一行可以引用目标表中的行。
不幸的是,至少对于SQL Server (a),唯一约束列中的NULL也必须是唯一的,尽管这会“破坏”NULL指令不等于任何值的SQL准则,包括另一个NULL。基本上,这种方法不适用于SQL Server。
出于这种窘境的一种可能方法(b)是具有可空列的外键约束,但是没有唯一约束。这将允许您确保您根本不引用目标表中的行(源表中的NULL)或引用目标行(源表中的任何非NULL值)。
但是,它不会为您提供“只有一个源行可以引用目标行”的要求。这可以添加一个before(插入/更新)触发器,该触发器将检查源表中的每隔一行,以确保没有其他行已经引用目标行。
你应该总是更喜欢数据库本身的约束。你永远不知道恶意应用程序(恶意或错误)何时会连接到你的数据库并决定不遵守规则。
(a)以下文字(here解释)显示了对几个DBMS产品中可为空的唯一列的不同支持:
标准:
如约束名称所示,具有UNIQUE约束的(一组)列可能只包含唯一(组合)值。
受UNIQUE约束约束的列或一组列也必须遵循NOT NULL约束,除非DBMS实现可选的“允许NULL”功能(功能ID 591)。可选功能为UNIQUE约束添加了一些额外的特性:
首先,UNIQUE约束中涉及的列也可能具有NOT NULL约束,但它们不必具有。其次,如果具有UNIQUE约束的列也没有NOT NULL约束,那么列可能包含任意数量的NULL'值'(NULL不等于NULL的逻辑结果)。
<强>的PostgreSQL:强>
遵循标准,包括可选的NULL允许功能。
<强> DB2:强>
遵循UNIQUE约束的非可选部分。不实现可选的NULL允许功能。
<强> MSSQL:强>
扭转标准。
MSSQL提供允许NULL的功能,但如果允许NULL,则允许最多只有一个NULL'value'实例。换句话说,它打破了上述标准描述中的特征2。
<强> MySQL的:强>
遵循标准,包括可选的NULL允许功能。
<强>甲骨文:强>
遵循标准,关于多列UNIQUE约束。
实现了可选的NULL允许功能:如果对单个列施加UNIQUE约束,则该列可以包含任意数量的NULL(如上面标准描述中的特征2所预期的那样)。但是,如果为多个列指定了UNIQUE约束,那么Oracle会将约束视为违反任何两行
(b)当然,另一种方法是选择实现此功能的DBMS,如PostgreSQL或MySQL。
在您的特定情况下可能无法实现,但至少应该预期。例如,我避开Oracle,因为它无法从某些字符列中的空字符串中辨别出NULL,虽然其他人不像我那样“纯粹主义”(我的妻子会说“肛门保持”): - )
答案 1 :(得分:4)
创建唯一的过滤索引:
CREATE UNIQUE INDEX Post_Unq_FbPost ON dbo.Post(FbPost) WHERE FbPost IS NOT NULL;
当然也要创建一个外键。
答案 2 :(得分:1)
Superclass It!
我认为最好的解决方案是使Post
表成为FBPost
表的超类。 (注意:我将在这里使用PostID
和FBPostID
来清楚地说明我所指的内容。)也就是说,从{{1}中删除FBPostID
列完全更新Post
列以匹配相应的FBPost.FBPostID
后的表格。它不是每个PostID
都有自己唯一的值,而是与FBPostID
共享相同的值。在我的专业观点中,这是正确的方式来建立一对一或一对一的关系。它具有万无一失的优点,除了简单的FK已经提供的功能外,不需要任何额外的索引,触发器或约束。
注意:我假设一旦我们放弃(假定的)PK,我们就可以更新PostID
中的FBPostID
列。如果它是FBPost
列,则需要更多工作 - 只需添加将成为新PK的新列,并重命名原始列。如果列顺序很重要,则必须将数据移动到新表中,以便在所需位置显示新列。
在处理此问题时,请务必考虑并发性,以便在更改发生时或更改更改的数据处理之前,不会不正确地读取或修改数据。
为什么选择
当你建模identity
关系而不是is-a
关系,并且这两个实体参与为0到零或一,那么他们应该共享相同的代理键,因为它们实际上是同一个实体(你实际上是在建模has-a
关系)。
即使更改为此数据库模型也需要一些工作,但修复次优设计是完全值得的。您的关系数据库的设计应始终利用可用的核心关系功能。你为什么要做其他事情:
is-sometimes-a
命令。类似地,您将使用外键/主键组合来建模CREATE TABLE
关系,而不必执行奇怪的NULL - 允许唯一约束或过滤索引来实现此目的。
示例更改脚本
这甚至都不太糟糕!这里有一些脚本可以让你很好地开始:
is-sometimes-a
最后,修改您的BEGIN TRAN;
ALTER TABLE dbo.Post DROP CONSTRAINT FK_Post_FBPostID; -- referencing FBPost
-- Also remove all FKs from any other tables referencing FBPostID
ALTER TABLE dbo.FBPost DROP CONSTRAINT PK_FBPost; -- FBPostID column not PK
ALTER TABLE dbo.FBPost ADD OriginalFBPostID int;
UPDATE dbo.FBPost WITH (HOLDLOCK, UPDLOCK) SET OriginalFBPostID = FBPostID;
UPDATE F
SET F.FBPostID = P.PostID
FROM
dbo.FBPost F
INNER JOIN dbo.Post P
ON F.OriginalFBPostID = P.FBPostID;
-- Perform a similar update on all other tables referencing FBPostID
-- Now, the two most important changes
ALTER TABLE dbo.FBPost ADD CONSTRAINT PK_FBPost PRIMARY KEY CLUSTERED (FBPostID);
ALTER TABLE dbo.FBPost ADD CONSTRAINT FK_FBPost_PostID FOREIGN KEY
REFERENCES dbo.Post (PostID); -- This is where the magic happens!
ALTER TABLE dbo.SomeTable ADD CONSTRAINT FK_SomeTable_FBPostID FOREIGN KEY
REFERENCES dbo.FBPost (FBPostID); -- and all other tables referencing FBPostID
EXEC sp_rename 'dbo.Post.FBPostID', 'OriginalFBPostID'; -- should stop using it
ALTER TABLE dbo.Post DROP COLUMN FBPostID; -- Or even better, remove it.
COMMIT TRAN;
ALTER TABLE dbo.FBPost DROP COLUMN OriginalFBPostID; -- Meaningless now
-- If you keep OriginalFBPostID and it is identity, please copy the values
-- to a new non-identity column and drop it so you don't keep generating more
/ Post
插入代码,将FBPost
用作PostID
。
新查询外观
只是为了推动这一点,你之间的联接看起来像这样:
FBPostID
但现在看起来像这样:
SELECT
P.Something,
F.SomethingElse
FROM
dbo.Post P
INNER JOIN dbo.FBPost F
ON P.FBPostID = F.FBPostID
现在问题彻底解决了!您的表格占用的空间更少(从SELECT
P.Something,
F.SomethingElse
FROM
dbo.Post P
INNER JOIN dbo.FBPost F
ON P.PostID = F.FBPostID -- the important part
丢失FBPostID
列)。您不必使用允许多个NULL的FK。在Post
表格中FBPostID
上的PK显示,每FBPost
行只能有一个FBPost
行。
答案 3 :(得分:0)
NULL能为你提供0 ... FK参考上的UNIQUE CONSTRAINT将为您提供1。