Fluent API 首先在 EF 代码中实现零或一到零或一的关系
Posted
技术标签:
【中文标题】Fluent API 首先在 EF 代码中实现零或一到零或一的关系【英文标题】:Implementing Zero Or One to Zero Or One relationship in EF Code first by Fluent API 【发布时间】:2013-01-20 00:23:56 【问题描述】:我有两个 POCO 课程:
订单类别:
public class Order
public int Id get; set;
public int? QuotationId get; set;
public virtual Quotation Quotation get; set;
....
引用类:
public class Quotation
public int Id get; set;
public virtual Order Order get; set;
....
每个Order
可以由一个或零个引号组成,并且
每个报价都可能导致订单。
所以我有一个“一或零”到“一或零”的关系,我该如何在EF
Code first by Fluent
API 中实现这一点?
【问题讨论】:
public virtual Quotation Quotation get; set;
,不是吗?你为什么不使用属性?为什么你的所有字段都是私有的?
对不起,我编辑了我的属性代码。我的课程不是虚拟的。
它不是一个虚拟类,它是一个虚拟导航属性,在 Order 类中。
为清楚起见,您可以使用***.com/a/45182785/1941942
【参考方案1】:
通过将 pocos 更改为:
public class Order
public int OrderId get; set;
public virtual Quotation Quotation get; set;
public class Quotation
public int QuotationId get; set;
public virtual Order Order get; set;
并使用这些映射文件:
public class OrderMap : EntityTypeConfiguration<Order>
public OrderMap()
this.HasOptional(x => x.Quotation)
.WithOptionalPrincipal()
.Map(x => x.MapKey("OrderId"));
public class QuotationMap : EntityTypeConfiguration<Quotation>
public QuotationMap()
this.HasOptional(x => x.Order)
.WithOptionalPrincipal()
.Map(x => x.MapKey("QuotationId"));
我们将拥有这个数据库(即 0..1-0..1):
特别感谢 (Mr. Vahid Nasiri)
【讨论】:
两种配置都有WithOptionalPrincipal
?
警告:您必须设置两个外键。如果只设置Order.Quotation
,则数据库中只设置Qotations.OrderId
,不设置Order.QuotationId
。
我试过这个并得到错误:'在类型'SQLiteTest.Entities.ObjectB'上声明的导航属性'ObjectA'已配置有冲突的外键。'!?
EF Core 中这种关系的替代品是什么
这不是真正的 0..1 到 0..1 关联,它是两个独立的此类关联。这是因为没有什么可以确保如果 A -> B 则 B 也 -> A。模型可以说一个报价单有一个订单,并且该订单有一个不同的报价单。 (例如:订单 1 的报价 1 分,但报价 2 的订单 1 分。)【参考方案2】:
@Masoud 的程序是:
modelBuilder.Entity<Order>()
.HasOptional(o => o.Quotation)
.WithOptionalPrincipal()
.Map(o => o.MapKey("OrderId"));
modelBuilder.Entity<Quotation>()
.HasOptional(o => o.Order)
.WithOptionalPrincipal()
.Map(o => o.MapKey("QuotationId"));
它给出:
通过将代码更改为:
modelBuilder.Entity<Order>()
.HasOptional(o => o.Quotation)
.WithOptionalPrincipal(o=> o.Order);
它给出:
【讨论】:
Masoud 的解决方案对我来说不能正常工作 - 这个解决方案似乎是正确的。 如何使用 DataAnnotations 做到这一点? 它如何知道如何在不定义任何键的情况下关联它们?【参考方案3】:参见http://msdn.microsoft.com/en-us/data/jj591620 EF 关系
一本好书 http://my.safaribooksonline.com/book/-/9781449317867
这是 2010 年 12 月开发人员发布的帖子。但仍然相关 http://social.msdn.microsoft.com/Forums/uk/adonetefx/thread/aed3b3f5-c150-4131-a686-1bf547a68804 上面的文章是一个很好的总结或可能的组合。
依赖表具有来自主表的键的解决方案是可能的。
如果您想要在 PK/FK 场景中都是 Principals 的独立密钥,我认为您不能使用 Fluent API 在 Code first 中做到这一点。如果他们共享一个密钥,你就可以了。 1:1 可选假设依赖项使用来自 Primary 的密钥。
但是因为您需要先保存其中一个表。您可以使用代码检查其中一个外键。或在 Code first 创建数据库后将第二个 Foreign 添加到数据库。
你会靠近的。但是,如果您希望两者都是外键,EF 会抱怨外键冲突。本质上,A 依赖于 B 依赖于 A EF 不喜欢,即使这些列在 DB 上是可空的并且在技术上是可行的。
这里用这个测试程序试试。只需注释掉 Fluent API 的内容即可尝试一些选项。 我无法让 EF5.0 与 INDEPENDENT PK/FK 0:1 到 0:1 配合使用 当然,正如所讨论的那样,有一些合理的妥协。
using System.Data.Entity;
using System.Linq;
namespace EF_DEMO
class Program
static void Main(string[] args)
var ctx = new DemoContext();
var ord = ctx.Orders.FirstOrDefault();
//. DB should be there now...
public class Order
public int Id get;set;
public string Code get;set;
public int? QuotationId get; set; //optional since it is nullable
public virtual Quotation Quotation get; set;
//....
public class Quotation
public int Id get;set;
public string Codeget;set;
// public int? OrderId get; set; //optional since it is nullable
public virtual Order Order get; set;
//...
public class DemoContext : DbContext
static DemoContext()
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<DemoContext>());
public DemoContext()
: base("Name=Demo")
public DbSet<Order> Orders get; set;
public DbSet<Quotation> Quotations get; set;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
modelBuilder.Entity<Order>().HasKey(t => t.Id)
.HasOptional(t => t.Quotation)
.WithOptionalPrincipal(d => d.Order)
.Map(t => t.MapKey("OrderId")); // declaring here via MAP means NOT declared in POCO
modelBuilder.Entity<Quotation>().HasKey(t => t.Id)
.HasOptional(q => q.Order)
// .WithOptionalPrincipal(p => p.Quotation) //as both Principals
// .WithOptionalDependent(p => p.Quotation) // as the dependent
// .Map(t => t.MapKey("QuotationId")); done in POCO.
;
【讨论】:
谢谢,你的回答对我有帮助,我找到了正确答案并贴出来。【参考方案4】:改编自answer,试试这个。
首先,修复你的类:
public class Order
public int Id get; set;
public virtual Quotation Quotation get; set;
// other properties
public class Quotation
public int Id get; set;
public virtual Order Order get; set;
// other properties
然后像这样使用 fluent API:
modelBuilder.Entity<Quotation>()
.HasOptional(quote => quote.Order)
.WithRequired(order=> order.Quotation);
基本上,对于 1:1 或 [0/1]:[0/1] 关系,EF 需要共享主键。
【讨论】:
谢谢,但是通过这种映射,我们将有一个零或一的关系。我找到了正确的答案并将其发布。【参考方案5】:public class OfficeAssignment [Key] [ForeignKey("Instructor")] public int InstructorID get; set; [StringLength(50)] [Display(Name = "Office Location")] public string Location get; set; public virtual Instructor Instructor get; set;
关键属性
Instructor 和 OfficeAssignment 实体之间存在一对零或一的关系。办公室分配仅与分配给它的讲师相关,因此它的主键也是其对 Instructor 实体的外键。但实体框架无法自动将 InstructorID 识别为该实体的主键,因为它的名称不遵循 ID 或 classnameID 命名约定。因此,使用 Key 属性将其标识为键:
https://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-a-more-complex-data-model-for-an-asp-net-mvc-application
【讨论】:
【参考方案6】:使用数据注释:
public class Order
[Key]
public int Id get; set;
public virtual Quotation Quotation get; set;
public class Quotation
[Key, ForeignKey(nameof(Order))]
public int Id get; set;
public virtual Order Order get; set;
【讨论】:
【参考方案7】:(请注意,这是使用 EF 6.4.4。)
只要您不想要外键属性,指定起来就相当简单:
modelBuilder
.Entity<Order>()
.HasOptional(o => o.Quotation)
.WithOptionalPrincipal(q => q.Order);
modelBuilder
.Entity<Quotation>()
.HasOptional(q => q.Order)
.WithOptionalDependent(o => o.Quotation);
注意这里WithOptionalPrincipal
和WithOptionalDependent
的用法。这应该在依赖端为您提供一个外键列(示例中的引号),但没有外键属性。如果你想要另一边的外键,切换“依赖”和“主体”。
(请注意,没有必要同时拥有以上两个定义;WithOptionalDependent
将暗示另一方是主体,反之亦然,因此如果需要,您可以只使用其中一个,但我发现指定关系双方通过双重声明来帮助防止错误;任何冲突都会导致模型错误,让您知道您错过了一些东西。)
虽然外键列上有索引,但该索引没有唯一约束。虽然可以添加自己的唯一约束(这需要Key IS NOT NULL
过滤器),但它似乎不起作用,并且在某些情况下更新关系时会出现异常。我认为这与 EF 将在单独的查询中执行其更新的“交换问题”有关,因此强制唯一性将阻止 EF 分两步“移动”一个键。
EF 似乎在内部处理关联本身,没有唯一的 DB 约束:
在任何一方,分配已使用的引用都会导致引用的其他用法被自动删除。 (所以如果在你打开上下文的时候已经是A1 B1的情况,然后你写A1 => B2,那么A1 B1被删除,A1 B2被添加,不管哪边你在。) 如果您尝试通过多次分配相同的引用来创建重复键,EF 将抛出异常,提示“违反多重性约束”。 (所以在相同的上下文中,您同时编写了 A1 => B1 和 A2 => B1,或者一些类似的冲突映射。) 如果您手动更新 DB 以创建重复键的情况,当 EF 遇到这种情况时,它会抛出一个异常,提示“发生关系多重性约束违规...这是一个不可恢复的错误。”在 EF6 中似乎不可能将属性映射到外键列(至少使用 Fluent API)。尝试这样做会导致非唯一列名异常,因为它会尝试分别为属性和关联使用相同的名称。
另请注意,拥有两个外键(即:两边各一个)在技术上是不正确的。这样的安排实际上是两个 0..1 到 0..1 的关联,因为没有什么可以说两端的键应该匹配。如果您通过 UI 和/或可能是某种数据库约束以其他方式强制关系,这可能会起作用。
我还注意到,对于 0..1 到 0..1 的关联究竟是什么,可能存在误解/误解。这意味着,根据我的理解和 EF 似乎也考虑它的方式,它是一个 1 对 1 的关联,双方都是可选的。因此,您可以在任何一方拥有没有关系的对象。 (而 1 到 0..1 的关联,一侧的对象可以在没有关系的情况下存在,但另一侧的对象总是需要一个对象来关联。)
但是 0..1 到 0..1 并不意味着您可以让关联在一个方向而不是另一个方向上传播。如果 A1 => B1,则 B1 => A1 (A1 B1)。如果不使 A1 与 B1 相关,则无法将 B1 分配给 A1。这就是为什么这个关联可以只使用一个外键。我认为有些人可能试图建立一个不正确的关联(A1 与 B1 相关,但 B1 与 A1 无关)。但这真的不是一个关联,而是两个 0..1 到 0..1 的关联。
【讨论】:
这与this answer 和其他两个答案完全相同。有什么理由再说一遍? @GertArnold 这不完全相同,它非常不同。该答案错误地创建了 两个 关联。这是正确显示如何创建单个 0..1 到 0..1 关联(使用流式 API)并解释这些关联的详细信息的唯一答案。 @GertArnold 实际上我看到您将 Kenneth 的答案与 Masoud 的答案联系起来。他的代码很相似,但没有解释,也没有人指出关联的一方应该是依赖的,而另一方是主体,这意味着什么。这是不仅概述关联的完整规范(使用流畅的 API)而且解释它的细节的唯一答案,例如如果您想要外键属性,这将不起作用。 (我不会回答,除非我正在记录我自己的试验和错误的结果,尽管这个问题的答案不正确或不完整。) 让我们continue this discussion in chat. 我在重试几个答案中找到的所有变体时有点迷失(我将删除我的 cmets),但事实仍然是 EF6 没有创建正确的模型。在数据库级别,它是 1:n。这都是我的主要观点的附属品:它重复了一个现有的答案。以上是关于Fluent API 首先在 EF 代码中实现零或一到零或一的关系的主要内容,如果未能解决你的问题,请参考以下文章
EF6:创建存储过程。使用 Fluent API 或 DBMigrations?
EF 代码优先 - 配置一对零或一关系,无需共享 PK/FK