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);

注意这里WithOptionalPrincipalWithOptionalDependent 的用法。这应该在依赖端为您提供一个外键列(示例中的引号),但没有外键属性。如果你想要另一边的外键,切换“依赖”和“主体”。

(请注意,没有必要同时拥有以上两个定义;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

EF 6.X 中的实体框架代码优先 Fluent API 默认值

在 numpy 中实现零均值和单位方差

如何在 ETl 中实现零停机

Code First Fluent API,我该怎么做?