多个 FK 关系约束中只有 1 个



>我有一个逻辑表结构,其中Table A与多个其他表(Table B&Table C(有关系。但在功能上Table A can only ever have the FK forBorC' 填充。

有效Table A记录:

|------|--------|---------|---------|
| ID   | Name   | FK_B    | FK_C    |
|------|--------|---------|---------|
| 1    | Record | -null-  | 3       |
|------|--------|---------|---------|

无效Table A记录:

|------|--------|---------|---------|
| ID   | Name   | FK_B    | FK_C    |
|------|--------|---------|---------|
| 1    | Record | 16      | 3       |
|------|--------|---------|---------|

我想知道如何正确定义此约束,因为与数据库接口的 API 代码不是唯一的门。

到目前为止,我有以下内容,但它不限制关系的单个记录 -null- 要求,并允许上面的两个示例。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TableA>()
.HasIndex(p => new { p.FK_B, p.FK_C });
}

如何定义此约束以要求仅填充这些 FK 列中的一个,而其他列为 null?

数据库的所有代码都是使用属性和流畅 API 的 EF Core Code First。原始SQL解决方案虽然可以接受,但在此项目中将更难管理。因此,我正在寻找一种适合这种限制的解决方案。

在花了一天时间研究这个问题并通过建议的答案和评论后,我找到了我一直在寻找的答案。似乎最新版本的EFCore回答了这个问题!

代码优先解决方案

需要 EFCore 3.0

在 EFCore 3.0 中引入了HasCHeckConstraint来提供代码优先解决方案来生成"检查约束"。以下示例说明了 SQL 检查语法接口。有一种语法采用布尔标志将 DataAnnotations 解释为约束,但此处的文档有限。文档

此解决方案比必须管理 SQL 脚本或手动迁移(如下(要好得多。尽管它仍然依赖于原始SQL语句。最终,如果用于此目的的 Fluent API 获取 CodeGen 方法版本,以便您可以提供 C# 函数而不是 SQL,以便在约束中的字段名称更改时提供更好的处理。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TableA>()
.HasCheckConstraint("CHK_OnlyOneForeignKey", "
( CASE WHEN FK_B IS NULL THEN 0 ELSE 1 END
+ CASE WHEN FK_C IS NULL THEN 0 ELSE 1 END
) = 1
");
}

EFCore 迁移编辑

在所有版本的 EFCore 中,您可以编辑生成的迁移文件,并利用MigrationBuilder上的Sql(...)方法来应用任何 SQL 命令。

注意:为此目的,通常会创建一个空的迁移。

public partial class CustomCheckConstraint : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("<SQL to alter target table and add a constraint>");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
// Code to remove Constraint added in Up method
}
}

感谢@fenixil和@Ian美世为我指出帮助我完善研究的解决方案。

有几种方法可以将验证合并到项目中:

SQL 服务器约束

阅读此线程 dba.stackexchange.com/q/5278/571 如何创建约束。

优点:保证数据一致性的最严格方法

缺点:迁移脚本中需要自定义 SQL;减慢数据库速度

建议:如果 DB 是集成点,则使用此选项;您无法控制写入该数据库的代码。

应用程序级别

保存更改验证

EF 有一个方法 ValidateEntity,您可以重写并检查是否未设置两个 FK。 EF Core 没有此 API。但是,您可以覆盖"保存更改"并使用ChangeTracker查找实体并对其进行验证。

优点:快速简单的解决方案

缺点:不保证持久性级别 (DB( 的一致性

遗产

SaveChanges 解决方案很简单,但它仍然允许用户做一些愚蠢的事情并获得运行时验证异常。为了防止这种情况,您可以通过引入类层次结构来强制使用编译类型检查来实施解决方案:A{Id,Name},B:Base {FK_B},C:Base {FK_C}。 EF 和 EF Core 都支持每个层次结构的表,因此所有实体都将存储在单个表中。在代码中,您只能将B(使用FK_B属性(或C(使用FK_C(添加到表中。

优点:限制您在编译时仅设置 1 FK

缺点:更复杂的解决方案,某些操作可能很复杂(例如将实体从C转换为B(;查询不太简单。

建议:如果在持久性级别工作并按原样向其他团队公开实体,请对要保存的内容进行少量控制。

最新更新