[英]Is it possible to capture a 0..1 to 0..1 relationship in Entity Framework?
有没有办法为实体框架中的可为空的外键关系创建可为空的反向导航属性? 在数据库用语中, 0..1 到 0..1 的关系。
我试过如下,但我不断收到错误消息:
无法确定类型“Type1”和“Type2”之间关联的主要端。 必须使用关系流畅 API 或数据注释显式配置此关联的主体端。
public class Type1 {
public int ID { get; set; }
public int? Type2ID { get; set; }
public Type2 Type2 { get; set; }
}
public class Type2 {
public int ID { get; set; }
public int? Type1ID { get; set; }
public Type1 Type1 { get; set; }
}
我知道整数列只能存在于一张表或另一张表中,但肯定应该可以涵盖所有必要的情况? 例如:
Type1 Type2
======== ===============
ID ID | Type1ID
-------- ---------------
1 1 | null
2 2 | 2
我试过使用数据注释(例如,一端使用[ForeignKey]
,两端使用[InverseProperty]
),但这些注释似乎都没有帮助。
如果可能,数据注释解决方案将优于 Fluent API。 还有, int?
如果有帮助的话,从域的角度来看,属性对于任何一个类都不是绝对必要的。
这里有一个有趣的解决方法,这意味着不可能在实体框架中捕获这种关系(实际上,一个项目是集合的可选部分) - 如果是这样,是否有任何文档支持这一点? .
在 EF6 和更早的版本中,正确实现这样的关联并不是那么容易。 幸运的是,EF-core 在支持的关联方面有了很大的改进。 现在实现唯一一个通过数据库约束强制执行这种关联的模型是小菜一碟。 即: Car
和Driver
之间的连接类,其中外键具有唯一索引(下面的选项 4)。 它甚至几乎完全适用于默认映射约定。
该模型:
class Car
{
public int ID { get; set; }
public string Brand { get; set; }
public CarDriver CarDriver { get; set; }
}
class Driver
{
public int ID { get; set; }
public string Name { get; set; }
public CarDriver CarDriver { get; set; }
}
class CarDriver
{
public int CarId { get; set; }
public int DriverId { get; set; }
public Car Car { get; set; }
public Driver Driver { get; set; }
}
唯一需要的显式映射:
class CarDriverConfig : IEntityTypeConfiguration<CarDriver>
{
public void Configure(EntityTypeBuilder<CarDriver> builder)
{
builder.HasKey(cd => new { cd.CarId, cd.DriverId });
}
}
这就是 EF 创建正确的数据库模型所需的全部内容:
CREATE TABLE [Car] (
[ID] int NOT NULL IDENTITY,
[Brand] nvarchar(max) NULL,
CONSTRAINT [PK_Car] PRIMARY KEY ([ID])
);
CREATE TABLE [Driver] (
[ID] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
CONSTRAINT [PK_Driver] PRIMARY KEY ([ID])
);
CREATE TABLE [CarDriver] (
[CarId] int NOT NULL,
[DriverId] int NOT NULL,
CONSTRAINT [PK_CarDriver] PRIMARY KEY ([CarId], [DriverId]),
CONSTRAINT [FK_CarDriver_Car_CarId] FOREIGN KEY ([CarId]) REFERENCES [Car] ([ID]) ON DELETE CASCADE,
CONSTRAINT [FK_CarDriver_Driver_DriverId] FOREIGN KEY ([DriverId]) REFERENCES [Driver] ([ID]) ON DELETE CASCADE
);
CREATE UNIQUE INDEX [IX_CarDriver_CarId] ON [CarDriver] ([CarId]);
CREATE UNIQUE INDEX [IX_CarDriver_DriverId] ON [CarDriver] ([DriverId]);
最后的这两个指标是锦上添花。 它们表明 EF 完全了解这里发生的事情。
原始但更新的答案
当我读到你的问题时,我想“这并不难”。 但是我再次发现一对一的关联充满了陷阱。 开始了。
我假设0..1 – 0..1
你的意思是两个对象可以相互独立存在,但也可能相互关联。
让我们把它具体化。 Car
和Driver
。 想象一下有许多汽车和司机,其中包括 CarA 和 DriverA。 现在假设您希望 CarA 与 DriverA 相关联,并且您的实现是 DriverA 将自己链接到 CarA。 但是一旦 DriverA 执行此操作,您希望 CarA 仅用于 DriverA, CarA 的关联不再是可选的,因此也应立即设置。
如何实施?
如果这是工作模型:
public class Car
{
public int CarId { get; set; }
public string Name { get; set; }
public int? DriverId { get; set; }
public virtual Driver Driver { get; set; }
}
public class Driver
{
public int DriverId { get; set; }
public string Name { get; set; }
public int? CarId { get; set; }
public virtual Car Car { get; set; }
}
技术上,DriverA可以有一个外键卡拉和卡拉的外键DriverB。
因此,当建立外键DriverA-CarA
,您应该“同时”建立反向外键CarA-DriverA
。 这是你应该在代码中做的事情,这意味着它是一个业务规则。 实际上,它不是原子操作,因此您必须确保它在一个数据库事务中完成。
类模型至少支持用例,但过于宽松。 它需要受到约束。 更重要的是,它不适用于 EF 。 EF 抱怨必须设置主要结束。 如果您这样做,EF 将不会创建双向关联。
在Driver
的映射配置中:
this.HasOptional(t => t.Car).WithMany().HasForeignKey(d => d.CarId);
在Car
的映射配置中:
this.HasOptional(t => t.Driver).WithMany().HasForeignKey(c => c.DriverId);
(没有数据注释替代方案)
我发现EF在创建新驱动程序和汽车时只在数据库中设置一个外键值。 您必须分别设置和保存两个关联,管理您自己的事务。 对于现有对象,您仍然必须设置两个外键,尽管这可以保存在一次SaveChanges
调用中。
更好的选择? 让我们来看看...
这是您引用的链接中提到的一对多关联。 这个模型需要外部约束,但创建关联是原子的。 而且你仍然在一端有一个参考,在另一端有一个集合。 它可以轻松地与 EF 映射。
您可以创建一个联结表CarDriver
,它有两个外键Car
和Driver
,这两个外键都包含其唯一的主键:
这是一个常规的多对多关联。 默认情况下,EF 会将其映射为类模型,其中Car
和Driver
具有相互指向的集合属性,并且不会直接映射连接表:
public class Car
{
public int CarId { get; set; }
public string Name { get; set; }
public virtual ICollection<Driver> Drivers { get; set; }
}
public class Driver
{
public int DriverId { get; set; }
public string Name { get; set; }
public virtual ICollection<Car> Cars { get; set; }
}
现在关联的创建是一个原子操作。 用 EF 映射这个模型是完全可能的。 相互引用消失了,但您仍然可以获取集合属性的FirstOrDefault()
作为代理引用。
但有一个重要的问题。 现在每个对象都可以有任意数量的关联副本。 如果您创建一个关联,您需要一个编码的业务规则来检查所涉及的对象是否还没有任何关联。 也许这个选项比选项 2 更糟糕。但我提到它是因为下一个选项:
选项 3 是原子的,但它也需要外部约束。 为了使关联互斥, CarDriver
两列CarDriver
应该有唯一的键,因此每个汽车或司机在表中只能出现一次。 通过这些索引,模型自己实现了双向可选的 1:1 关联。 任何处理它的代码都必须遵守规则。 安然无恙...
在HasIndex
,由于引入了HasIndex
,这可以通过以下映射来实现:
modelBuilder.Entity<Car>().HasOptional(c => c.CarDriver).WithRequired();
modelBuilder.Entity<Driver>().HasOptional(c => c.CarDriver).WithRequired();
modelBuilder.Entity<CarDriver>().HasKey(cd => new { cd.CarId, cd.DriverId });
modelBuilder.Entity<CarDriver>().HasIndex(cd => cd.CarId).IsUnique();
modelBuilder.Entity<CarDriver>().HasIndex(cd => cd.DriverId).IsUnique();
但是,由于 EF6 默认在 FK 字段上添加索引,唯一索引添加到默认非唯一索引之上。 所以还是需要人工干预迁移代码来去除后者。
选项 1 最接近您想要的。 但我不喜欢设置两个外键的义务,它很容易被未来的开发人员遗忘或忽略。
但选项 2 和 3 在可被遗忘的编码业务规则方面有更重的要求。 当代理“1”结束时,集合是不自然的。 选项 3 对我有一些吸引力,因为Car
和Driver
在数据库中是完全独立的,并且关联是带有不可为空的外键的记录(DBA 也倾向于这样)。
选项 4 具有相同的吸引力,当多个应用程序必须实现需要强加给选项 2 和 3 的外部约束时,它是最佳选择。此外,即使忘记了编码规则,数据库约束也是最后的问题。 但它不能轻易地被 EF6 实现。
这不是您使用实体框架构建表的方式。 这些类的正确声明是:
public class Type1 {
public int ID { get; set; }
}
public class Type2 {
public int ID { get; set; }
public virtual Type1 @Type1 { get; set; }
}
编辑:我认为做你想做的最简单的方法是:
public class Type1 {
public int ID { get; set; }
public virtual ContainerClass {get; set;}
}
public class Type2 {
public int ID { get; set; }
public virtual ContainerClass {get; set;}
}
public class ContainerClass {
public int ID {get;set;}
public virtual Type1 @Type1 {get;set;}
public virtual Type2 @Type2 {get;set;}
}
从内存中执行此操作,因此遗憾的是未测试:
public Type1
{
[Key]
public int ID { get; set; }
[ForeignKey("Type2")]
public int? Type2ID { get; set; }
public virtual Type2 Type2 { get; set; }
}
public Type2
{
[Key]
public int ID { get; set; }
[ForeignKey("Type1")]
public int? Type1ID { get; set; }
public virtual Type1 Type1 { get; set; }
}
顺便说一句,在实体上使用显式 ForeignKey 允许测试是否存在关联对象,而不必调用数据库。 例如IEnumerable<Type1>.Where(t => t.Type2ID.HasValue)
将返回所有具有关联Type2
的Type1
对象! 有关更多详细信息,请参阅此问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.