你的模型不是一对一关联。你仍然可以有许多 Class2
对象引用同一个 Class1
对象。此外,你的模型不能保证一个引用了 Class1
的 Class2
也被这个 Class1
对象引用回来——Class1
可以引用任何一个 Class2
对象。
如何配置一对一关联?
在 SQL 中保证(某种程度上)一对一关联的常见方法是为主要实体和从属实体各创建一个表,在从属表中将主键同时设为对应主要表的外键:
![1:1](https://istack.dev59.com/eiRnN.webp)
(这里的 Class1
是主要实体)
然而,在关系数据库中,这仍无法保证一对一关联(这就是我说“某种程度上”的原因)。它是一个1:0..1关联。可能会存在一个没有 Class2
的 Class1
。事实上,在 SQL 中真正的一对一关联是不可能的,因为没有语言构造可以同时在不同的表中插入两行数据。1:0..1 是我们能够得到的最接近一对一关联的方式。
流畅映射
为了在 EF 中建模这种关联,你可以使用流畅 API。以下是标准的操作方式:
class Class1Map : EntityTypeConfiguration<Class1>
{
public Class1Map()
{
this.HasKey(c => c.Id);
this.Property(c => c.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.HasRequired(c1 => c1.Class2).WithRequiredPrincipal(c2 => c2.Class1);
}
}
而在这个背景下:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new Class1Map());
}
这是你的课程剩余部分:
public class Class1
{
public int Id {get;set;}
public virtual Class2 Class2 {get;set;}
}
public class Class2
{
public int Id {get;set;}
public virtual Class1 Class1 {get;set;}
}
模型中没有办法配置备用的外键属性,因为唯一涉及的外键必须是从属实体的主键。
这个模型的奇怪之处在于EF不会阻止您创建(并保存)一个没有class2
的class1
对象。我认为EF应该能够在保存更改之前验证此要求,但显然它没有这么做。同样,有方法可以删除一个class2
对象而不删除其class1
父级。因此,这个HasRequired
- WithRequired
组合并不像看起来那么严格(也应该是如此)。
数据注释
唯一正确的方式在代码中实现这一点是通过数据注释。(当然,数据库模型仍然无法强制执行1:1)
public class Class1
{
public int Id {get;set;}
[Required]
public virtual Class2 Class2 {get;set;}
}
public class Class2
{
[Key, ForeignKey("Class1")]
public int Id {get;set;}
[Required]
public virtual Class1 Class1 {get;set;}
}
[Key, ForeignKey("Class1")]
这个注解告诉EF,Class1
是主体实体。
数据注释在许多API中都发挥着作用,在某些情况下可能会带来麻烦,因为每个API都选择实现其自己的子集。但这里非常方便,因为现在EF不仅使用它们来设计数据模型,还用于验证实体。如果你试图保存一个没有 class2
的class1
对象,你将收到一个验证错误提示。