是否可以在Entity Framework中捕获0..1到0..1的关系?

有没有办法在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? 对于任何一个类,从域的角度来看,属性并不是必需的,如果这有帮助的话。

这里有一个有趣的解决方法,这意味着不可能在entity framework中捕获这种关系(实际上,一个项目可选择是集合的一部分) – 如果是这样,是否有任何文档支持这一点? 。

当我读到你的问题时,“这可不难”。 但我再次发现,一对一的联想是危险的混蛋。 开始了。

我假设通过0..1 – 0..1表示两个对象可以彼此独立存在,但也可以彼此唯一关联。

让它具体化。 CarDriver 。 想象一下,有许多汽车和司机,其中包括CarA和DriverA。 现在假设您希望CarA与DriverA相关联,并且您的实现是DriverA将自己链接到CarA。 但是,只要DriverA执行此操作,您希望CarA仅用于DriverA, CarA的关联不再是可选的 ,因此它应该立即设置。

如何实现?

选项1:

如果这是工作模式:

 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可以将CarACarA的外键作为DriverB的外键。

在此处输入图像描述

因此,当建立外键DriverA-CarA ,您应该“同时”建立反向外键CarA-DriverA 。 这是你应该在代码中做的事情,这意味着它是一个商业规则 。 实际上,它不是primefaces操作,因此您必须确保它在一个数据库事务中完成。

类模型至少支持用例,但它太宽容了。 它需要受到限制。 更重要的是, 它不适用于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调用中。

更好的选择? 让我们来看看…

选项2:

这是您所指的链接中提到的一对多关联。 此模型需要外部约束,但创建关联是primefaces的。 你还有一端的参考和另一端的集合。 它可以轻松映射到EF。

选项3:

您可以创建一个具有两个外键的联结表CarDriverCarDriver ,它们都包含其唯一的主键:

在此处输入图像描述

这是一个常规的多对多关联。 默认情况下,EF会将此映射为类模型,其中CarDriver具有指向彼此的集合属性,并且未直接映射联结表:

 public class Car { public int CarId { get; set; } public string Name { get; set; } public virtual ICollection Drivers { get; set; } } public class Driver { public int DriverId { get; set; } public string Name { get; set; } public virtual ICollection Cars { get; set; } } 

现在,关联的创建是一个primefaces操作。 使用EF映射此模型是完全可能的。 相互引用已经消失,但您仍然可以将集合属性的FirstOrDefault()作为代理引用。

但是有一个重要的问题。 现在每个对象可以有任意数量的关联对应物。 如果您创建了关联,则需要一个编码的业务规则来检查所涉及的对象是否还没有任何关联。 也许这个选项比选项2更差。但是我提到它是因为下一个选项:

选项4

选项3是primefaces的,但它也需要外部约束。 要使关联独占, CarDriver两列都应具有唯一键 ,因此每个汽车或驱动程序只能在表中出现一次。 通过这些索引,模型实现了双向可选的1:1关联。 使用它的任何代码都必须遵守规则。 安然无恙…

但你想要吗?

您永远不会首先在EF代码中配置选项4。 不是流畅的映射,也不是数据注释。 您必须使用迁移来执行此操作,或者首先使用数据库。

如果数据库由多个应用程序使用,则最佳选择可能是确定数据模型中的约束。 如果规则不太可能改变,这也可能是一个可行的选择。 但是,如果只有一个(或两个)应用程序在数据库上工作,并且业务规则将来可能会发生变化,那么我更倾向于使用带有编码规则的更自由的数据模型。 编码业务逻辑比数据模型更容易更改。

此外,即使使用选项4,您还需要业务逻辑来在尝试创建关联之前检查关联的存在,否则丑陋的数据库exception将为您执行此操作。

结论

选项1最接近您的要求。 但我不喜欢设置两个外键的义务,很容易被未来的开发人员遗忘或忽视。

但是,对于可以忘记的编码业务规则而言,选项2和3的要求更高。 代理“1”结束时,collections品不自然。 选项3对我有一些吸引力,因为CarDriver在数据库中是完全独立的,并且关联是具有不可为空的外键的记录(DBA也倾向于这样)。

选项4具有相同的吸引力,当多个应用程序必须实现需要对选项2和3施加的外部约束时,它是最佳选择。此外,即使忘记了编码规则,数据库约束也是最终的限制。 但它不能轻易地通过EF代码实现。

这不是您如何使用entity framework构建表。 这些类的正确声明是:

 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.Where(t => t.Type2ID.HasValue)将返回Type1中具有关联Type2所有对象! 有关详细信息,请参阅此问题 。