在上一篇,大概介紹了Entity Framework Core關於關係映射的邏輯。在上一篇中留下了EF的外鍵映射沒有說,也就是一對一,一對多,多對一,多對多的關係等。這一篇將為大家細細分析一下,如何設置這些映射。 1. 實體之間的關係 從數據表來考慮,兩個表之前的關係有一對一,一對多(多對一)和多對 ...
在上一篇,大概介紹了Entity Framework Core關於關係映射的邏輯。在上一篇中留下了EF的外鍵映射沒有說,也就是一對一,一對多,多對一,多對多的關係等。這一篇將為大家細細分析一下,如何設置這些映射。
1. 實體之間的關係
從數據表來考慮,兩個表之前的關係有一對一,一對多(多對一)和多對多的關係。
其中一對一,指的是表A有一條記錄對應著表B最多有一條記錄與之對應。反過來也一樣,表A也最多有一條記錄與表B的某一條記錄對應。具體在數據表上表現為,A表和B表各有一個外鍵指向對方。
一對多和多對一是一個概念,只是參考的方向是相反的。所謂的一對多就是其中多方上有一個屬性或者列指向了另一個實體,而那個“一”的那頭則沒有對應的屬性指向多方。
多對多是指兩個類的實例各有一個集合屬性指向對方,換句話說就是A有0到多個B,B也有0到多個A。這裡有一個關於多對多的ER圖。
2. 一對一關係
先給出兩個示例類,為了方便理解,我只保留了主鍵和導航屬性:
public class SingleModel
{
public int Id { get; set; }
public SingleTargetModel SingleTarget { get; set; }
}
public class SingleTargetModel
{
public int Id { get; set; }
public SingleModel Single { get; set; }
}
那麼我們開始寫配置文件:
public class SingleModelConfig : IEntityTypeConfiguration<SingleModel>
{
public void Configure(EntityTypeBuilder<SingleModel> builder)
{
builder.ToTable("SingleModel");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
var relation = builder.HasOne(t => t.SingleTarget).WithOne(r => r.Single);
}
}
public class SingleTargeModelConfig : IEntityTypeConfiguration<SingleTargetModel>
{
public void Configure(EntityTypeBuilder<SingleTargetModel> builder)
{
builder.ToTable("SingleTargetModel");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
}
}
其中HasOne表示當前實體是關係中“一”,WithOne 表示導航目標類的關係。
當然,如果直接應用這兩個配置到EF Context的話,在執行
Update-Database
會報以下錯誤:
The child/dependent side could not be determined for the one-to-one relationship between 'SingleModel.SingleTarget' and 'SingleTargetModel.Single'. To identify the child/dependent side of the relationship, configure the foreign key property. If these navigations should not be part of the same relationship configure them without specifying the inverse. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details.
意思就是無法定義一對一關係中的子/從屬方
如何解決呢?之前在說的時候,EF會根據導航屬性自動生成一個外鍵,但是這一條在一對一這裡就有點不太起作用了。所以我們必須手動在導航屬性的一側實體類里配置外鍵,並用 HasForeignKey指定。(如果不使用Fluent API,也是需要在一端實體類配置外鍵,另一端則不需要)。
修改後:
public class SingleModel
{
public int Id { get; set; }
public int TargetId { get; set; }
public SingleTargetModel SingleTarget { get; set; }
}
public class SingleTargetModel
{
public int Id { get; set; }
public SingleModel Single { get; set; }
}
所以最終的配置應該如下:
public class SingleModelConfig : IEntityTypeConfiguration<SingleModel>
{
public void Configure(EntityTypeBuilder<SingleModel> builder)
{
builder.ToTable("SingleModel");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
builder.HasOne(t => t.SingleTarget).WithOne(r => r.Single).HasForeignKey<SingleModel>(t=>t.TargetId);
}
}
public class SingleTargeModelConfig : IEntityTypeConfiguration<SingleTargetModel>
{
public void Configure(EntityTypeBuilder<SingleTargetModel> builder)
{
builder.ToTable("SingleTargetModel");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
//builder.HasOne(t => t.Single).WithOne(r => r.SingleTarget).HasForeignKey<SingleTargetModel>("SingleId");
}
}
註意我註釋的這一行,現在EF只在SingleModel表中生成了一個外鍵關係,在檢索SingleTargetModel的時候,EF會從SingleModel表中檢索對應的外鍵關係,並引入進來。
如果取消這行註釋,EF會在SingleTargetModel表添加一個名為SingleId並指向SingleModel的外鍵,而取消SingleModel里的外鍵。
但是,這時候如果在SingleTargetModel里添加了一個非空屬性的SingleId,SQLite插入數據時會報錯。錯誤信息:
SQLite Error 19: 'FOREIGN KEY constraint failed'.
其他資料庫提示,外鍵不能為空。
所以也就是說EF不推薦這種雙方互導航的一對一關係。
這是生成的DDL SQL語句:
create table SingleModel
(
Id INTEGER not null
constraint PK_SingleModel
primary key autoincrement,
TargetId INTEGER not null
constraint FK_SingleModel_SingleTargetModel_TargetId
references SingleTargetModel
on delete cascade
);
create unique index IX_SingleModel_TargetId
on SingleModel (TargetId);
create table SingleTargetModel
(
Id INTEGER not null
constraint PK_SingleTargetModel
primary key autoincrement
);
3. 一對多或多對一
照例,先來兩個類:
public class OneToManySingle
{
public int Id { get; set; }
public List<OneToManyMany> Manies { get; set; }
}
public class OneToManyMany
{
public int Id { get; set; }
public OneToManySingle One { get; set; }
}
如果從OneToManySingle來看,這個關係是一對多,如果從OneToManyMany來看的話這個關係就是多對一。
那麼我們看一下一對多的配置吧:
public class OneToManySingleConfig : IEntityTypeConfiguration<OneToManySingle>
{
public void Configure(EntityTypeBuilder<OneToManySingle> builder)
{
builder.ToTable("OneToManySingle");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
builder.HasMany(t => t.Manies)
.WithOne(p => p.One);
}
}
public class OneToManyManyConfig : IEntityTypeConfiguration<OneToManyMany>
{
public void Configure(EntityTypeBuilder<OneToManyMany> builder)
{
builder.ToTable("OneToManyMany");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
//builder.HasOne(p => p.One).WithMany(t=>t.Manies);
}
}
在使用隱式外鍵的時候,只需要設置導航屬性的關聯即可。如果想在Single端設置,需要先用 HasMany表示要設置一個多對X的關係,然後調用WithOne 表示是多對一。如果是Many端,則必須先聲明是HasOne。
其中 WithXXX里的參數可以省略,如果只是配置了單嚮導航的話。
如果顯示聲明瞭外鍵,需要用HasForeignKey來標註外鍵。
以下是生成的DDL SQL語句:
create table OneToManySingle
(
Id INTEGER not null
constraint PK_OneToManySingle
primary key autoincrement
);
create table OneToManyMany
(
Id INTEGER not null
constraint PK_OneToManyMany
primary key autoincrement,
OneId INTEGER
constraint FK_OneToManyMany_OneToManySingle_OneId
references OneToManySingle
on delete restrict
);
create index IX_OneToManyMany_OneId
on OneToManyMany (OneId);
4. 多對多
在講多對多的時候,需要先明白一個概念。多對多,對於導航兩端來說,是無法在自己身上找到對應的標記的。也就是說,各自的數據表不會出現指向對方的外鍵。那麼,如何實現多對多呢?增加一個專門的中間表,用來存放兩者之間的關係。
EF Core中取消了在映射關係中配置中間表的功能,所以在EF Core中需要一個中間表:
public class ManyToManyModelA
{
public int Id { get; set; }
public List<ModelAToModelB> ModelBs { get; set; }
}
public class ModelAToModelB
{
public int Id { get; set; }
public ManyToManyModelA ModelA { get; set; }
public ManyToManyModelB ModelB { get; set; }
}
public class ManyToManyModelB
{
public int Id { get; set; }
public List<ModelAToModelB> ModelAs { get; set; }
}
那麼繼續看一下配置文件:
public class ManyToManyToModelAConfig : IEntityTypeConfiguration<ManyToManyModelA>
{
public void Configure(EntityTypeBuilder<ManyToManyModelA> builder)
{
builder.ToTable("ManyToManyModelA");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
builder.HasMany(t => t.ModelBs).WithOne(p => p.ModelA);
}
}
public class ManyToManyModelBConfig : IEntityTypeConfiguration<ManyToManyModelB>
{
public void Configure(EntityTypeBuilder<ManyToManyModelB> builder)
{
builder.ToTable("ManyToManyModelB");
builder.HasKey(p => p.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
builder.HasMany(t => t.ModelAs).WithOne(p => p.ModelB);
}
}
與一對多的關係不同的地方是,這個需要兩方都配置一個多對一的映射,指向中間表。
在EF 6中 中間表可以僅存在於關係中,但是在EF Core3 還沒有這個的支持。也就是當前文章使用的版本。
5. 附加
在EF的外鍵約束中,導航屬性是預設可空的。如果要求非空,也就是導航屬性的另一端必須存在則需要在配置關係的時候添加:
IsRequired()
這個方法也用來聲明欄位是必須的。這個驗證是在EF 調用 SaveChanges 的時候校驗的。
6. 未完待續
照例的未完待續,下一篇將為大家介紹一下EF Core 在開發中的用法。
更多內容煩請關註我的博客《高先生小屋》