【问题标题】:One to one relationship - code first一对一关系——代码优先
【发布时间】:2013-05-27 11:53:46
【问题描述】:

我正在尝试建立一对一的关系。如果没有必要,我不想使用 fluent API。这是我迄今为止尝试过的:

 [Table("UserProfile")]
public class UserProfile
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }
    public string UserName { get; set; }
}
  public class Person
{
    public int Id { get; set; }

    [ForeignKey("UserId")]
    public UserProfile UserProfile { get; set; }

}

是的,我知道几乎没有类似的问题,但没有一个是简短而清晰的。其中很多也没有工作。

【问题讨论】:

  • 谢谢。投反对票有帮助。
  • 我们能否检查您使用的是哪个版本的 MVC 和 EF - 这可能对您的回答很重要。
  • MVC 4,实体框架 5
  • 好的,已经测试了我的答案,它有效。看看它是否适合你?

标签: sql asp.net-mvc entity-framework asp.net-mvc-4


【解决方案1】:

这在一定程度上取决于您想要实现的表结构类型。有多种方法可以做到这一点,所有选项都有一个很好的演练,从这些链接中的Shared Primary Key AssocationsOne-to-One Foreign Key Associations。不幸的是,这些链接比 Annotations 更多地使用 Fluent。下面的示例根据需要使用注释。

共享主键

理论上共享主键(水平表分区,在数据库方面)是“正确的方式”。这也是生成迁移所需的最小更改(将使用共享主键关联)。请注意,我会将Person.Id 更改为Person.UserId 以更好地显示您的意图:

// tested in EF 5 and MVC 4.5.
[Table("UserProfile")]
public class UserProfile {
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }
    public string UserName { get; set; }
}
[Table("Person")] // not required, added for clarity in sample code
public class Person {
    // Note the change of property name to reflect that this is a shared primary key,
    //  using the UserId column in UserProfile as the Primary Key
    [Key]
    public int UserId { get; set; } 
    [ForeignKey("UserId")]
    public virtual UserProfile UserProfile { get; set; }
}
// The generated migration:
public partial class AddTable_Person : DbMigration
{
    public override void Up() {
        CreateTable(
            "dbo.Person",
            c => new {
                    UserId = c.Int(nullable: false),
                })
            .PrimaryKey(t => t.UserId)
            .ForeignKey("dbo.UserProfile", t => t.UserId)
            .Index(t => t.UserId);
    }
    public override void Down(){
        DropIndex("dbo.Person", new[] { "UserId" });
        DropForeignKey("dbo.Person", "UserId", "dbo.UserProfile");
        DropTable("dbo.Person");
    }
}

这实际上为您提供了1:0-1 之间的1:0-1 关系UserProfile(这是强制性的)和People(这是可选的,但每个人最多可以有一个。

如果您想在Person 中使用Id,请执行以下操作(迁移将相应更改):

public class Person {
    public int Id { get; set; }

    [ForeignKey("Id")]
    public UserProfile UserProfile { get; set; }
}

双向导航的共享主键

如果您想从UserProfile 导航到Person,您还有更多工作要做。只需将public virtual Person Person { get; set; } 添加到 UserProfile 就会出现错误:

无法确定类型“Test.Models.UserProfile”和“Test.Models.Person”之间关联的主体端。此关联的主体端必须使用关系流式 API 或数据注释显式配置。

因此,我们在Person.UserProfile 属性上使用[Required] 修复它(Person 需要UserProfile)。这提供了与以前相同的迁移。

// tested in EF 5 and MVC 4.5.
[Table("UserProfile")]
public class UserProfile {
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }
    public string UserName { get; set; }
    [ForeignKey("UserId")]
    public virtual Person Person { get; set; }
}
[Table("Person")] // not required, added for clarity in sample code
public class Person {
    [Key]
    public int UserId { get; set; }
    [ForeignKey("UserId")]
    [Required]
    public virtual UserProfile UserProfile { get; set; }
}

同样,如果您将Id 用于Person 而不是UserId,则此方法有效:

public class Person {
    [Key]
    public int Id { get; set; }
    [ForeignKey("Id")]
    [Required]
    public virtual UserProfile UserProfile { get; set; }
}

【讨论】:

  • +1:很好的分析!我发现奇怪的是,在Person.UserId 上添加[ForeignKey("UserProfile")] 的双向导航在没有[Required] 属性和UserProfile 中没有FK 属性的情况下工作,但是在没有[Required] 属性的情况下放置[ForeignKey("UserId")] 不起作用在 Person.UserProfile - 除非您删除 UserProfile.Person 导航属性。完全让我困惑...
  • @Slauma。确实如此,尽管我更喜欢在virtual UserProfile 属性上使用Required,因为它有助于声明意图——不过纯粹是个人喜好。
【解决方案2】:
[Table("UserProfile")]
public class UserProfile
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }
    public string UserName { get; set; }
    public virtual Person Person {get;set;}
}

public class Person
{
    public int Id { get; set; }

    public int UserProfileUserId { get; set; } //Foreign Key
    public virtual UserProfile UserProfile { get; set; }
}

【讨论】:

  • 它不起作用。这就是为什么我说这个话题确实有大量不正确的答案。这就是为什么我如此喜欢投反对票的人。
【解决方案3】:

如果您想创建一对一的关系,您首先必须澄清什么是主体,什么是该关系中的依赖实体。 Person 可以在没有UserProfile 的情况下存在,或者UserProfile 可以在没有Person 的情况下存在吗?

因为您已经开始在Person 中应用[ForeignKey] 属性,所以我现在假设Person 是依赖项,即如果没有UserProfile,它就无法存在。

[ForeignKey] 属性的正确应用是:

public class Person
{
    [ForeignKey("UserProfile")]
    public int Id { get; set; }

    public UserProfile UserProfile { get; set; }
}

我不确定这是否足够,因为您在 UserProfile 中没有 Person 导航属性。如果不起作用,请将此属性添加到 UserProfile:

public Person Person { get; set; }

如果你不想拥有这样的导航属性,你就无法避免 Fluent API:

modelBuilder.Entity<Person>()
    .HasRequired(p => p.UserProfile)
    .WithOptional();

【讨论】:

  • 恕我直言,modelBuilder.Entity&lt;Person&gt;() .HasRequired(p =&gt; p.UserProfile) .WithOptional(); 是用户不想使用的 FLUENT API 代码..
  • 我可以使用fluent api,没问题,我也想要导航属性,是的,没有UserProfile就不能存在Person
  • @BhushanFirake:“如果没有必要”,他说。但如果他不想拥有UserProfile.Person 导航属性,则(可能)是必要的。他可以选择:导航属性或 Fluent API。可能即使没有导航属性也没有必要,因为 FK 属性指的是 PK,也许 EF 足够聪明,可以确定同时是 PK 的 FK 只能参与一对一的关系。我不确定。
  • 多么令人惊讶,它不起作用。无法确定类型之间关联的主要结束...这是我到目前为止在 UserProfile 表中的内容:int Id(主键),字符串 UserName,导航属性 Person Person。在 Person 表中,我有 int Id(主键)和导航属性 UserProfile UserProfile,其中有 [ForeignKey("Id")] 注释。
  • @impeRAtoR:是的,令人惊讶...[ForeignKey] 属性必须位于Id 属性上:[ForeignKey("UserProfile")]。请参阅我编辑的代码。我不知道为什么这对一对一的关系很重要。对于一对多关系,两种版本(FK 或导航属性上的 FK 属性)都受支持。
猜你喜欢
  • 1970-01-01
  • 2012-03-06
  • 2015-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多