理解实体框架Code First中的ForeignKey属性

32
请参考以下帖子了解背景信息:Entity framework one to zero or one relationship without navigation property 我一直认为ForeignKey是用于显示类中哪个属性持有ForeignKey,从而确定导航属性,例如:
public class MemberDataSet
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public int? DeferredDataId { get; set; }
    [ForeignKey("DeferredDataId")]
    public virtual DeferredData DeferredData { get; set; }
}

然而,我在链接的帖子中发现这是不正确的,由于 DeferredData 的主键被称为 Id,因此我实际上需要:

public class MemberDataSet
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public int? DeferredDataId { get; set; }
    [ForeignKey("Id")]
    public virtual DeferredData DeferredData { get; set; }
}

ForeignKey 用于指向另一个类。

然后我继续更改了一些其他引用:

public class MemberDataSet
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public int? DeferredDataId { get; set; }
    [ForeignKey("Id")]
    public virtual DeferredData DeferredData { get; set; }

    public int? SignedOffById { get; set; }
    [ForeignKey("UserId")]
    public virtual UserProfile SignedOffBy { get; set; }
}

然而,这次尝试失败了。结果发现,在这个实例中,ForeignKey需要指向MemberDataSet类的Id。

public class MemberDataSet
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public int? DeferredDataId { get; set; }
    [ForeignKey("Id")]
    public virtual DeferredData DeferredData { get; set; }

    public int? SignedOffById { get; set; }
    [ForeignKey("SignedOffById")]
    public virtual UserProfile SignedOffBy { get; set; }
}

我认为这是因为第二个关系是一对多,而第一个关系是一对零或一,实际上关系的主要端点不同,但我希望能够得到一些明确的解释/好的文章引用,以便我能理解正在发生的事情并准确了解ForeignKey的作用。

在上面的示例中,我还寻求清晰度,即 public int? DeferredDataId {get;set;} 如何适用于等式,因为它没有明确链接到 DeferredData。我很高兴这将通过约定匹配,但如果它有不同的名称,我应该如何明确告诉它呢?我看到的所有示例都提到使用 ForeignKey 属性,但根据上面的情况,这不能是所有情况的答案!

非常感谢所有帮助 - 我想了解问题而不是解决特定问题,因为我的模型中有很多引用,所以需要确定每种方法。

谢谢。

编辑

添加其他类以帮助:

public class DeferredData
{

    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    //other properties
}

public class UserProfile
{

    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int UserId { get; set; }

    //other properties
}
3个回答

33

1..0关系中所需的MemberDataSet一侧不应该有一个指向DeferredData的FK。相反,DeferredData的PK也应该是MemberDataSet的FK(称为共享主键)。

public class MemberDataSet
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public virtual DeferredData DeferredData { get; set; }
}

public class DeferredData
{
    // DeferredData.Id is both the PK and a FK to MemberDataSet
    [Key]
    [DatabaseGenerated( DatabaseGeneratedOption.None )]
    [ForeignKey( "MemberDataSet" )]
    public int Id { get; set; }

    [Required]
    public virtual MemberDataSet MemberDataSet { get; set; }
}

流畅的API:

modelBuilder.Entity<MemberDataSet>()
    .HasOptional( mds => mds.DeferredData )
    .WithRequired()
    .WillCascadeOnDelete();

1
不,你正在引用当前类的PK字段,该字段显然与“Id”具有相同的名称。在这种情况下不会使用“DeferredDataId”(作为FK)。 - Moho
1
太好了!在DeferredData中没有导航属性的情况下,这是否可能实现? - Mark007
1
不使用属性,您将需要使用流畅的API。 - Moho
1
modelBuilder.Entity<MemberDataSet>().HasOptional(d => d.DeferredData).WithRequired().WillCascadeOnDelete(true); 然后去掉ForeignKey属性?找不到IsForeignKey类型选项。 - Mark007
1
“虚拟”导航属性仅意味着代理可以惰性加载该属性 - 这与FK无关。这是“ForeignKey”属性的标准用法,只是应用于实体的PK字段,以强制执行1:0关系。 - Moho
显示剩余5条评论

0

我认为你的原始想法是正确的,只有一个小例外。通过将外键放在MemberDataSet上,你暗示了你希望建立一个零或一对多的关系。

在你的例子中,MemberDataSet.DeferredData是可选的,并且DeferredData可以被多个MemberDataSet实例引用。

用流畅的语法来表达这个关系:

modelBuilder.Entity<MemberDataSet>()
    .HasOptional(dataSet => dataSet.DeferredData)
    .WithMany()
    .HasForeignKey(deferredData => deferredData.DeferredDataId);

为了将其变成一对零或一属性,您可以在MemberDataSet的DeferredDataId列上放置一个唯一(非空)键约束。这意味着一个DeferredData实体只能被单个MemberDataSet实体引用。
CREATE UNIQUE INDEX unique_MemberDataSet_DeferredDataId ON MemberDataSet(DeferredDataId) WHERE DeferredDataId IS NOT NULL

注意:此类过滤键仅适用于SQL Server 2008及以上版本。


0

我认为你是对的(如果我理解正确的话)。[ForeignKeyAttribute]用于相应的外键上,而不是您链接对象的主键。

这是我的对象,外键是DeferredDataId

您对象中的Id既不是外键(它是主键),也不是链接对象的ID是外键(它是关系另一侧的主键)

希望我理解正确 :) 因为我不确定。


我喜欢这个描述:“这是我的对象,外键是DeferredDataId”。这就是我一直以来的想法。然而,这无法构建数据库,解决方案是改为使用Id,即根据链接的帖子引用其他类。 - Mark007
我检查了我的一个较旧的项目。我按照这种方式完成了它(而且它也运行正常)。环境是使用SQL CE 4和Entity Framework Code First的WCF数据服务。但我不认为这很相关。你有收到任何错误吗? - csteinmueller
错误信息为:“在类型'MemberDataSet'的属性'DeferredData'上的ForeignKeyAttribute无效。从属类型'DeferredData'中未找到外键名称'DeferredDataId'。”我认为@Moho已经解释了这个问题,现在开始有些明白了! - Mark007
我将链接到MSDN文章。在“关系属性:InverseProperty和ForeignKey”章节中,解释正是这样的。另一种方法是使用虚拟关系属性。EF Code First将自动创建外键。http://msdn.microsoft.com/en-us/data/gg193958.aspx - csteinmueller
谢谢,我之前看过那个,但是不知道如何将其应用到我正在做的事情上。现在一切都清晰了! - Mark007

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