使用外键引用映射多对多关系

本文关键字:关系 映射 引用 | 更新日期: 2023-09-27 18:36:03

对于精通EF的用户来说,这应该是一个简单的问题。

我有以下架构(在我的脑海中)表之间的关系应该如何。

[FooBar]      [Foo]          [Bar]
FooId PK,FK   Id PK          Id PK
BarId PK,FK   BarId FK       Name
IsRead        Name           Description
              Description    

但是,当我尝试使用 EF 代码优先生成架构时,它无法解释实体之间的关系,因为我已经解释它们(将外键FooId添加到[bar]表),并且无法完全创建[FooBar]桥表。

如果有人可以指导我如何使用 EF4 代码优先实现上述架构,我将不胜感激。解决方案是否涉及我的 POCO 模型的属性、流畅配置或两者的混合并不重要 - 只要创建了所需的数据库架构即可。


POCO 型号:

public class Foo
{
    public int Id { get; set; }
    public string Text { get; set; }
    public string Description { get; set; }
    public int BarId { get; set; }
    public Bar Bar { get; set; } /* bar entity */
    public virtual ICollection<Bar> BridgedBars { get; set; }
    public Foo()
    {
        Bars = new List<Bar>();
    }
}
public class Bar
{
    public int Id { get; set; }
    public string Text { get; set; }
    public string Description { get; set; }
    public virtual ICollection<Foo> Foos { get; set; }
    public virtual ICollection<Foo> BridgedFoos { get; set; }
    public Bar()
    {
        Foos = new List<Foo>();
        BridgedFoos = new List<Foo>();
    }
}
public class FooBar
{
    public int FooId { get; set; }
    public int BarId { get; set; }
    public virtual Foo Foo { get; set; }
    public virtual Bar Bar { get; set; }
    public bool IsRead { get; set; }
}

使用外键引用映射多对多关系

您的模型确实会在Bar中创建一个外键FooId,该外键属于Foo.BrideBars定义的关系。EF 不会将此导航属性与Bar中的ICollection<Foo>属性之一相关联,因为其中有两个属性,EF 无法唯一确定哪个是正确的对。因此,它会为另一端没有导航属性的Foo.BrideBars创建关系。可以这么说,有一个不可见的Bar.Foo属性会导致外键。

要映射到模型的数据库模式实际上并不表示多对多关系,而是与中间"桥接"实体FooBar的两个一对多关系。必须在导航属性中使用此类来定义正确的关系。它看起来像这样:

public class Foo
{
    public int Id { get; set; }
    public string Text { get; set; }
    public string Description { get; set; }
    public int BarId { get; set; }
    public Bar Bar { get; set; }
    public virtual ICollection<FooBar> FooBars { get; set; }
}
public class Bar
{
    public int Id { get; set; }
    public string Text { get; set; }
    public string Description { get; set; }
    public virtual ICollection<Foo> Foos { get; set; }
    public virtual ICollection<FooBar> FooBars { get; set; }
}
public class FooBar
{
    [Key, Column(Order = 0)]
    public int FooId { get; set; }
    [Key, Column(Order = 1)]
    public int BarId { get; set; }
    public virtual Foo Foo { get; set; }
    public virtual Bar Bar { get; set; }
    public bool IsRead { get; set; }
}

此模型中的命名约定将检测正确的关系。仅对于FooBar实体,才需要显式定义键,因为属性名称不符合约定(无Id,也没有FooBarId属性)。在此模型中,在 FooBar 中使用复合键是有意义的。

我猜,您的实际类和属性没有名称 FooBar .如果您的真实姓名不遵循约定,则可能必须指定与注释或Fluent API的关系:

modelBuilder.Entity<Foo>()
    .HasRequired(f => f.Bar)
    .WithMany(b => b.Foos)
    .HasForeignKey(f => f.BarId);
modelBuilder.Entity<FooBar>()
    .HasKey(fb => new { fb.FooId, fb.BarId }); // replaces the [Key] annotations
modelBuilder.Entity<FooBar>()
    .HasRequired(fb => fb.Foo)
    .WithMany(f => f.FooBars)
    .HasForeignKey(fb => fb.FooId);
modelBuilder.Entity<FooBar>()
    .HasRequired(fb => fb.Bar)
    .WithMany(b => b.FooBars)
    .HasForeignKey(fb => fb.BarId);

在数据库架构中,FooBar表将具有复合主键:

[FooBar]       [Foo]          [Bar]
FooId PK,FK    Id PK          Id PK
BarId PK,FK    BarId FK       Name
IsRead         Name           Description
               Description    

但是,FooBar中具有 PK 是必要的,因为 EF 模型中的每个实体都必须定义一个键属性(单个或复合),该属性映射到数据库表中的主键。

在这个问题中 - 首先创建代码,多对多,关联表中的其他字段 - 更详细地介绍如何使用这种类型的关系。(有时人们也称之为"与有效负载的多对多关系"(IsRead属性是示例模型中的"有效负载"),但实际上它不是多对多。