在Entity Framework中,1:1关系中的“principal end”是什么意思?

277
public class Foo
{
    public string FooId{get;set;}
    public Boo Boo{get;set;}
}


public class Boo
{
    public string BooId{get;set;}
    public Foo Foo{get;set;}
}

我在尝试使用Entity Framework时遇到了以下错误:

无法确定类型为“ConsoleApplication5.Boo”和“ConsoleApplication5.Foo”之间关联的主端。此关联的主端必须使用关系 Fluent API 或数据注释显式配置。

我在 StackOverflow 上看到了针对此错误的解决方案,但我想了解术语“主端”的含义。


请访问 https://learn.microsoft.com/zh-cn/ef/core/modeling/relationships 了解这些术语的解释。 - DeepSpace101
3个回答

384

在一对一的关系中,必须有一个端点是主要的,而另一个端点是从属的。主要端是首先插入的并且可以存在于没有从属端的情况下。从属端是必须在主要端之后插入的,因为它具有指向主要端的外键。

在实体框架中,从属端的外键也必须是其主键,因此在您的情况下应使用:

public class Boo
{
    [Key, ForeignKey("Foo")]
    public string BooId{get;set;}
    public Foo Foo{get;set;}
}

或流畅映射

modelBuilder.Entity<Foo>()
            .HasOptional(f => f.Boo)
            .WithRequired(s => s.Foo);

6
@Ladislav,我需要创建两个独立的表格,它们之间都有一个可选的相互引用关系(一对一),我希望它们都有自己的主键,这是否可能?我已经发布了一个单独的问题 - Shimmy Weitzhandler
10
你不知道寻找答案花了多少时间 - 微软的文档太烂了,谢谢你。 - gangelo
1
请注意,在VS2012中,您可能需要添加using System.ComponentModel.DataAnnotations.Schema;以获取ForeignKey。 - Stuart Dobson
2
这是否意味着 Foo 是主体呢? - bflemi3
8
@bflemi3,你是正确的,“Boo”是依赖项,需要“Foo”,并获得外键。 “Foo”是主体,可以存在而不需要“Boo”。 - Colin
显示剩余8条评论

187

您还可以使用[Required]数据注释属性来解决此问题:

public class Foo
{
    public string FooId { get; set; }

    public Boo Boo { get; set; }
}

public class Boo
{
    public string BooId { get; set; }

    [Required]
    public Foo Foo {get; set; }
}

FooBoo 必需的。


这对于我想要将两者之间的映射作为单独实体的以下代码是正确的:public class Organisation { public int Id { get; set; }} public class user { public int Id { get; set; }} public class UserGroup { [Key] public int Id { get; set; } [Required] public virtual Organisation Organisation { get; set; } [Required] public virtual User User { get; set; } } - AndyM
我正在使用Oracle,但是没有任何流畅的API适用于我。谢谢兄弟。如此简单。 - CameronP
11
请注意,当使用这个解决方案时,如果您尝试更新刚从数据库检索出来的 Boo 对象,除非您先触发 Foo 属性的延迟加载,否则会出现验证异常。https://entityframework.codeplex.com/SourceControl/network/forks/BrandonDahler/EntityFramework/contribution/7703 - NathanAldenSr
2
“Boo Boo” 应该是虚函数吗? - Simon_Weaver
1
@NathanAldenSr,链接现在失效了,您如何进行更改? - CamHart
通过FluentAPI配置没有起作用,仍然不确定原因 - 但是在从属类中的主属性上添加“Required”解决了问题。 - JeremyW

9
这是关于@Ladislav Mrnka在使用流畅API配置一对一关系的答案。
有这样一种情况,即没有可能将依赖项的FK作为其PK。
例如,Foo已经与Bar存在一对多的关系。
public class Foo {
   public Guid FooId;
   public virtual ICollection<> Bars; 
}
public class Bar {
   //PK
   public Guid BarId;
   //FK to Foo
   public Guid FooId;
   public virtual Foo Foo;
}

现在,我们需要在Foo和Bar之间添加另一个一对一的关系。
public class Foo {
   public Guid FooId;
   public Guid PrimaryBarId;// needs to be removed(from entity),as we specify it in fluent api
   public virtual Bar PrimaryBar;
   public virtual ICollection<> Bars;
}
public class Bar {
   public Guid BarId;
   public Guid FooId;
   public virtual Foo PrimaryBarOfFoo;
   public virtual Foo Foo;
}

以下是使用Fluent API指定一对一关系的方法:

modelBuilder.Entity<Bar>()
            .HasOptional(p => p.PrimaryBarOfFoo)
            .WithOptionalPrincipal(o => o.PrimaryBar)
            .Map(x => x.MapKey("PrimaryBarId"));

请注意,添加PrimaryBarId时需要删除,因为我们通过流畅的 API 指定它。
还请注意,方法名[WithOptionalPrincipal()][1]有点讽刺。在这种情况下,主体是 Bar。 在 MSDN 上WithOptionalDependent() 的描述使其更加清晰明了。

2
如果您实际上 想要 PrimaryBarId 属性怎么办?这对我来说太荒谬了。如果我添加该属性并指定它为外键,那么就会出现错误。但是如果我没有该属性,则 EF 仍将创建它。有什么区别呢? - Chris Pratt
1
@ChrisPratt 这可能听起来不太合理。但这是我在试错后得出的解决方案。当我在Foo实体中有PrimayBarId属性时,无法配置一对一映射。你也尝试过同样的解决方案吗?或许是EF的限制所致? - Sudarshan_SMD
3
是的,我发现到至今为止,EF从未实现唯一索引。因此,映射一对一关系的唯一方法是将主端的主键作为依赖端的主键,因为主键本质上是唯一的。换句话说,他们只是半实现了它,并采取了一种捷径,这要求你的表必须按非标准方式设计。 - Chris Pratt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接