我正在尝试执行以下操作:
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”。因为 Dependent Role 属性不是关键属性,上限 依赖角色的重数必须是“*”。
这里可能出现什么问题?
您的模型不是 1:1 关联。您仍然可以有 many
Class2
对象引用同一个 one Class1
对象。此外,您的模型不保证引用 Class2
的 Class1
也被此 Class1
对象引用 — Class1
可以引用任何 Class2
对象。
在 SQL 中保证(某种程度上)1:1 关联的常见方法是为principal实体创建一个表,并为dependent实体创建一个表,其中从属表中的主键也是校长:
(这里
Class1
是主体)
现在在关系数据库中,这仍然不能保证 1:1 关联(这就是我说“某种程度上”的原因)。这是一个 1:0..1 关联。可以有
Class1
而没有 Class2
。事实上,真正的 1:1 关联在 SQL 中是不可能的,因为没有语言结构可以在不同的表中同步插入两行。 1:0..1 是我们得到的最接近的值。
流畅的映射
要在 EF 中对这种关联进行建模,您可以使用 Fluent 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;}
}
无法在模型中配置备用外键属性,因为涉及的唯一 FK 必须是依赖者的主键。
此模型的奇怪之处在于,EF 不会阻止您创建(和保存)一个
class1
对象,而没有 class2
。我认为 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 不仅使用它们来
设计数据模型,还验证实体。现在,如果您尝试保存没有 class1
的
class2
对象,您将收到验证错误。
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
...