因此,如果我理解正确,您的问题在于将一映射到零或一关系。
您正在体验的是实体框架的设计特性。由于很多原因,处理一对一的关系(以及它们的可选对应关系)是很棘手的。当你这样做时,你不能在你的模型中指定外键——相反,你的实体的主键也将是主体端的外键,不能指定额外的 FK。
有关此的更多详细信息,请参见下文。
映射一对零或一
假设您有以下模型:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
}
public class Car
{
public int CarId { get; set; }
public string LicensePlate { get; set; }
}
public class MyDemoContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Car> Cars { get; set; }
}
现在你想设置它,以便你可以表达以下规范:一个人可以有一辆或零辆汽车,每辆汽车完全属于一个人(关系是双向的,所以如果 CarA 属于 PersonA,那么PersonA“拥有”CarA)。
所以让我们稍微修改一下模型:添加导航属性和外键属性:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public int CarId { get; set; }
public virtual Car Car { get; set; }
}
public class Car
{
public int CarId { get; set; }
public string LicensePlate { get; set; }
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
以及配置:
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasRequired(c => c.Person).WithOptional(p => p.Car);
}
}
此时这应该是不言自明的。这辆车有一个必需的人 (HasRequired()),而这个人有一辆可选的汽车 (WithOptional())。同样,从哪一方配置此关系并不重要,只是在使用 Has/With 和 Required/Optional 的正确组合时要小心。从Person 方面来看,它看起来像这样:
public class PersonEntityTypeConfiguration : EntityTypeConfiguration<Person>
{
public PersonEntityTypeConfiguration()
{
this.HasOptional(p => p.Car).WithOptional(c => c.Person);
}
}
现在让我们看看 db 架构:
仔细看:可以看到People中没有FK可以引用Car。此外,Car 中的 FK 不是PersonId,而是CarId。这是 FK 的实际脚本:
ALTER TABLE [dbo].[Cars] WITH CHECK ADD CONSTRAINT [FK_dbo.Cars_dbo.People_CarId] FOREIGN KEY([CarId])
REFERENCES [dbo].[People] ([PersonId])
所以这意味着我们在模型中拥有的CarId 和PersonId 外键属性基本上都被忽略了。它们在数据库中,但它们不是外键,正如预期的那样。这是因为一对一映射不支持将 FK 添加到您的 EF 模型中。那是因为一对一的映射在关系数据库中是很成问题的。
这个想法是每个人都可以拥有一辆车,而那辆车只能属于那个人。或者可能有人员记录,其中没有与之关联的汽车。
那么如何用外键表示呢?显然,Car 中可能有一个PersonId,People 中可能有一个CarId。为了强制每个人只能拥有一辆汽车,PersonId 在Car 中必须是唯一的。但是如果PersonId 在People 中是唯一的,那么如何在PersonId 是NULL 的地方添加两条或更多记录(不止一辆没有车主的汽车)?答:不能(实际上,您可以在 SQL Server 2008 和更新版本中创建过滤的唯一索引,但让我们暂时忘记这个技术性;更不用说其他 RDBMS)。更不用说指定关系两端的情况了......
如果People 和Car 表具有“相同”主键(连接记录中的值相同),则唯一真正执行此规则的方法。要做到这一点,Car 中的 CarId 必须既是 People PK 的 PK 又是 FK。这使整个架构变得一团糟。当我使用它时,我宁愿将 PK/FK 命名为 Car PersonId,并进行相应的配置:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public virtual Car Car { get; set; }
}
public class Car
{
public string LicensePlate { get; set; }
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
public class CarEntityTypeConfiguration : EntityTypeConfiguration<Car>
{
public CarEntityTypeConfiguration()
{
this.HasRequired(c => c.Person).WithOptional(p => p.Car);
this.HasKey(c => c.PersonId);
}
}
不理想,但可能会好一点。不过,在使用此解决方案时您必须保持警惕,因为它违反了通常的命名约定,这可能会导致您误入歧途。这是从此模型生成的架构:
所以这种关系不是由数据库模式强制执行的,而是由实体框架本身强制执行的。这就是为什么你在使用它时必须非常小心,不要让任何人直接对数据库进行修改。