【问题标题】:Implementing Zero Or One to Zero Or One relationship in EF Code first by Fluent APIFluent API 首先在 EF 代码中实现零或一到零或一的关系
【发布时间】: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 类中。
  • 为清楚起见,您可以使用stackoverflow.com/a/45182785/1941942

标签: c# entity-framework ef-code-first entity-relationship fluent


【解决方案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 Code{get;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)而且解释它的细节的唯一答案,例如如果您想要外键属性,这将不起作用。 (我不会回答,除非我正在记录我自己的试验和错误的结果,尽管这个问题的答案不正确或不完整。)
      • 我在重试几个答案中找到的所有变体时有点迷失(我将删除我的 cmets),但事实仍然是 EF6 没有创建正确的模型。在数据库级别,它是 1:n。这都是我的主要观点的附属品:它重复了一个现有的答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-14
      • 1970-01-01
      • 2018-07-01
      • 2014-05-07
      相关资源
      最近更新 更多