【问题标题】:EF6 One-To-One relationship with separate ID propertyEF6 具有单独 ID 属性的一对一关系
【发布时间】:2018-01-18 08:28:36
【问题描述】:

我们需要定义以下实体之间的一对一关系:

public class Foo
{
    [Key, Column("Foo_ID")]
    public int Id { get; set; }

    public Bar Bar { get; set; }
}

public class Bar
{
    [Key, Column("Bar_ID")]
    public int Id { get; set; }

    [Column("Bar_Foo_ID")]
    public int? FooId { get; set; }

    public Foo Foo { get; set; }   
}

我们需要迁移旧版软件,并暂时让它们同时运行。所以我们不能改变任何关系。不幸的是,数据库中没有定义外键。

modelBuilder.Entity<Foo>()
            .HasOptional(a => a.Bar)
            .WithRequired(x => x.Foo)
            .WillCascadeOnDelete(true);

当我们查询 FooInclude(x =&gt; x.Bar) 时,它会创建一个带有 LEFT OUTER JOIN bars ON Foo_ID = Bar_ID 的 SQL 查询,这是错误的。

我们需要将其更改为LEFT OUTER JOIN bars on Foo_ID = Bar_Foo_ID,但我不确定实体框架如何支持它,因为我们的数据库中没有外键,Bar_ID 作为 PrimaryKey。

我知道一个Foo 可能有多个Bars,但是有没有办法强制一对一的关系?

创建Foo 时,始终会为其创建Bar

【问题讨论】:

  • 一对一关系(甚至任何关系)没有外键?!我不知道是否可以这样做。但是您可以阅读此内容,可能会对entityframeworktutorial.net/code-first/… 有所帮助还有另一件事,如果Bar 总是为 Foo 创建的,为什么还要LEFT JOIN?!
  • 已经去过那里,但不幸的是这不起作用。我从 SQL Profiler 获得了LEFT OUTER JOIN。该查询由 LINQ2Entities 生成。
  • 如果您想要精确的一对一关系(不是一对零或一),请阅读此stackoverflow.com/questions/10292355/…
  • 好吧,我错了,我想要实现的是一对零或一的关系,它“事实上”从不为零,因为我们的代码确保在创建记录时创建父实体。
  • @grek40 似乎有一个合理的解决方法,但如果这不合适,您始终可以考虑 not 映射导航属性并使用 joinJoin 手动加入在你自己的记录。除此之外,我模糊地记得HasOne在这里很有用。

标签: c# .net entity-framework


【解决方案1】:

对于遗留数据,您可以使用以下内容:

1 : 0-1 关系基本上是 1 : many 关系的一种特殊形式。

因此,您可以在实体框架中将关系配置为 1:many,并可能向外键属性添加唯一索引以强制执行您的最大值约束。一个相关条目。

public class Foo
{
    [Key, Column("Foo_ID")]
    public int Id { get; set; }

    // this can't be a single reference property unfortunately... but it will only contain 0 or 1 object when working with it.
    public ICollection<Bar> Bars { get; set; }
}

public class Bar
{
    [Key, Column("Bar_ID")]
    public int Id { get; set; }

    [Index(IsUnique = true)]
    [Column("Bar_Foo_ID")]
    public int? FooId { get; set; }

    public Foo Foo { get; set; }   
}

modelBuilder.Entity<Foo>()
            .HasMany(a => a.Bars)
            .WithRequired(x => x.Foo)
            .HasForeignKey(x => x.FooId)
            .WillCascadeOnDelete(true);

【讨论】:

    【解决方案2】:

    按照我的理解,Bar 表中有一个名为 Bar_Foo_ID 的现有列,它必须用作Bar 表中的 PK Bar_ID 列的 FK。

    这是可能的,但 EF6 对这种关系的支持有限,特别是不支持显式 FK 属性。所以你必须从模型中删除FooId 属性:

    public class Bar
    {
        [Key, Column("Bar_ID")]
        public int Id { get; set; }
    
        public Foo Foo { get; set; }   
    }
    

    并使用以下流畅的配置:

    modelBuilder.Entity<Foo>()
        .HasOptional(a => a.Bar)
        .WithOptionalPrincipal(x => x.Foo)
        .Map(m => m.MapKey("Bar_Foo_ID"))
        .WillCascadeOnDelete(true);
    

    MapKey是指定FK列名。 WithOptionalPrincipal 是指定它是可空的(而WithRequired 将是不可空的)。

    请注意,EF6 对可选关系的级联删除处理方式不同。它不是删除关联的记录,而是通过将 FK 设置为 null 来解除关联。如果您想在删除 Foo 时实际删除关联的 Bar,则必须使用代码手动执行此操作。

    【讨论】:

    • Bar_Foo_ID 是表Foo 的PK Foo_ID 的FK,但没有在数据库中定义FK。假设它是“不存在的”FK
    • 如果您映射到现有数据库,这并不重要。它只是向 EF 提供必要的信息,例如,如何连接表,这是一个问题。通过此设置,您应该获得所需的LEFT JOIN
    • 感谢您的回答,但如何在删除 FooId 实体后创建 Bar 实体?当我创建一个Foo 实体时,我总是创建一个Bar 实体,将其FooId 设置为之前创建的Foo 实体的ID。
    • 您将改为设置bar.Foo 属性。或者foo.Bar,如果你愿意的话。换句话说,使用导航属性。由于答案中提到的EF6限制,没有其他办法。
    猜你喜欢
    • 1970-01-01
    • 2020-04-08
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多