EF Code-First一对一关系:在关系中,角色*的多重性无效。

37

我正在尝试做以下事情:

public class class1
{
    public int Id {get;set;}
    [ForeignKey("Class2")]
    public int Class2Id {get;set;}
    public virtual Class2 Class2 {get;set;}
}

public class class2
{
    public int Id { get; set;}
    [Required]
    public virtual int Class1Id {get;set;}
    [Required]
    [ForeignKey("Class1Id")]
    public Class1 Class1 {get;set;}
}

然而,每次尝试迁移数据库时,我都会遇到以下错误:

Class1_Class2_Target: :在关系“Class2_Class1”中,角色'Class2_Class1_Target'的多重性无效。由于从属角色属性不是关键属性,因此从属角色的上限必须为'*'。

这里可能有什么问题呢?

3个回答

71

你的模型不是一对一关联。你仍然可以有许多 Class2 对象引用同一个 Class1 对象。此外,你的模型不能保证一个引用了 Class1Class2 也被这个 Class1 对象引用回来——Class1 可以引用任何一个 Class2 对象。

如何配置一对一关联?

在 SQL 中保证(某种程度上)一对一关联的常见方法是为主要实体和从属实体各创建一个表,在从属表中将主键同时设为对应主要表的外键:

1:1

(这里的 Class1 是主要实体)

然而,在关系数据库中,这仍无法保证一对一关联(这就是我说“某种程度上”的原因)。它是一个1:0..1关联。可能会存在一个没有 Class2Class1。事实上,在 SQL 中真正的一对一关联是不可能的,因为没有语言构造可以同时在不同的表中插入两行数据。1:0..1 是我们能够得到的最接近一对一关联的方式。

流畅映射

为了在 EF 中建模这种关联,你可以使用流畅 API。以下是标准的操作方式:

class Class1Map : EntityTypeConfiguration<Class1>
{
    public Class1Map()
    {
        this.HasKey(c => c.Id);
        this.Property(c => c.Id)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
        this.HasRequired(c1 => c1.Class2).WithRequiredPrincipal(c2 => c2.Class1);
    }
}

而在这个背景下:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Configurations.Add(new Class1Map());
}

这是你的课程剩余部分:

public class Class1
{
    public int Id {get;set;}
    public virtual Class2 Class2 {get;set;}
}

public class Class2
{
    public int Id {get;set;}
    public virtual Class1 Class1 {get;set;}
}

模型中没有办法配置备用的外键属性,因为唯一涉及的外键必须是从属实体的主键。

这个模型的奇怪之处在于EF不会阻止您创建(并保存)一个没有class2class1对象。我认为EF应该能够在保存更改之前验证此要求,但显然它没有这么做。同样,有方法可以删除一个class2对象而不删除其class1父级。因此,这个HasRequired - WithRequired组合并不像看起来那么严格(也应该是如此)。

数据注释

唯一正确的方式在代码中实现这一点是通过数据注释。(当然,数据库模型仍然无法强制执行1:1)

public class Class1
{
    public int Id {get;set;}
    [Required]
    public virtual Class2 Class2 {get;set;}
}

public class Class2
{
    [Key, ForeignKey("Class1")]
    public int Id {get;set;}
    [Required]
    public virtual Class1 Class1 {get;set;}
}

[Key, ForeignKey("Class1")]这个注解告诉EF,Class1是主体实体。

数据注释在许多API中都发挥着作用,在某些情况下可能会带来麻烦,因为每个API都选择实现其自己的子集。但这里非常方便,因为现在EF不仅使用它们来设计数据模型,还用于验证实体。如果你试图保存一个没有 class2class1对象,你将收到一个验证错误提示。


流畅的API方法非常棒,与注释方法相比,它更能理解配置。+1。 - theTechRebel

2

我有完全相同的问题。 我想要的是数据库模式具有两个表,这些表通过[外键] -> [主键]交叉引用。 最后我找到了解决方法: 假设我们有两个类:Books和Authors。 Book类应该有一个外键指向其作者,而Author类应该有一个外键指向他最后写的书。 使用Code First让EF理解这一点的方法是: (请注意,这是使用数据注释和流畅API的混合完成的)

public class Book {
    ...
    public Guid BookId
    ...
    public Guid AuthorId { get; set; }

    [ForeignKey("AuthorId")]
    public virtual Author author { get; set; }
}

public class Author {
    ...
    public Guid AuthorId
    ...
    public Guid? LatestBookId { get; set; }

    [ForeignKey("LatestBookId")]
    public virtual Book book { get; set; }

    public virtual ICollection<Book> books { get; set; }
}

// using fluent API
class BookConfiguration : EntityTypeConfiguration<Book> {

    public BookConfiguration() {
        this.HasRequired(b => b.author)
            .WithMany(a => a.books);
    }

}

这个代码可以创建我想要的数据库模式。在SQL中,它将创建表和外键,对应以下代码:

CREATE TABLE [dbo].[Book](
    [BookId] [uniqueidentifier] NOT NULL,
    [AuthorId] [uniqueidentifier] NOT NULL,
    ...
 CONSTRAINT [PK_dbo.Book] PRIMARY KEY CLUSTERED 
(
    [BookId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

...

GO

ALTER TABLE [dbo].[Book] WITH CHECK ADD  CONSTRAINT [FK_dbo.Book.Author_AuthorId] FOREIGN KEY([AuthorId])
REFERENCES [dbo].[Author] ([AuthorId])
GO

...

CREATE TABLE [dbo].[Author](
    [AuthorId] [uniqueidentifier] NOT NULL,
    [LatestBookId] [uniqueidentifier] NULL,
    ...
 CONSTRAINT [PK_dbo.Author] PRIMARY KEY CLUSTERED 
(
    [AuthorId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

...

GO

ALTER TABLE [dbo].[Author]  WITH CHECK ADD  CONSTRAINT [FK_dbo.Author_dbo.Book_LatestBookId] FOREIGN KEY([LatestBookId])
REFERENCES [dbo].[Book] ([BookId])
GO

...

6
这不是一对一的关系。 - Ozzy
这不是解决方案。 - Asiri Dissanayaka

-1
其中的两个类必须在另一个之前创建,因此需要使用 [Required] 注释。如果 Class2 依赖于 Class1,则指定 [Required, ForeignKey("Class1")]。您还可以在上下文类中使用流畅的 API 进行配置。

我尝试在Class2中的外键上添加[Required]数据注释,但仍然出现相同的错误。(我也已经更新了我的代码,在问题中展示了这一点) - JensOlsen112
啊,你的外键值应该引用每个类中的int值,而不是类对象。因此,外键应该是“Class1Id”,而不是“Class1”。Class2也是如此。 - RizJa
1
我也尝试过(已更新我的问题代码),但仍然出现相同的错误。 - JensOlsen112
奇怪的事情:当我删除Class1Id和Class2Id属性时,就不再出现错误了 - 是什么原因? - JensOlsen112

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