准備工作
根據教程前幾節,已經建立好了三個實體類,並且生成了數據庫。三個實體類分別是:
聯賽League:
public class League
{
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
[Required, MaxLength(50)]
public string Country { get; set; }
}
球員Player:
public class Player
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
俱樂部Club:
public class Club
{
public Club()
{
Players = new List<Player>();
}
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
[Column(TypeName = "date")]
public DateTime DateOfEstablished { get; set; }
public string History { get; set; }
public League League { get; set; } // 導航屬性
public List<Player> Players { get; set; } // 導航屬性
}
一對多的關系
Club有一個導航屬性League,導航到單個League上,一個Club都對應一個League,但可能有多個Club對應到同一個League上,所以Club對League就是多對一的關系。所以Club表中應該存在一個外鍵對應League,在代碼中看不出,可以在數據庫中看一下:
確實存在。這種關系可以在代碼中指定,不指定也會自動生成。
Club的Players屬性也是另一種形式的導航屬性,這個導航屬性的類型是一個集合,相當於另一個方向的導航。這個時候Club是主表,Player是子表。Players表中也有一個ClubId外鍵。
多對多的關系
計划再創建一個比賽Game實體模型,它與球員Player之間是m:n的關系。這種關系使用EF Core無法直接實現,可以加一個中間表GamePlayer。比如一個隊員,這個賽季參加了5場比賽,它就應該對應5哥GamePlayer,一對多的關系。而每場比賽,又有多個隊員參加,每個隊員又相當於這場比賽的一個GamePlayer,所以Game和GamePlayer也是一對多的關系。這樣Player和Game之間就相當於間接形成了多對多的關系。
比賽Game:
public class Game
{
public int Id { get; set; }
public int Round { get; set; }
public DateTimeOffset? StartTime { get; set; }
}
因為StartTime是具體的時間,所以用DateTimeOffset類型。
再建一個GamePlayer類:
該類是Games和Players的中間表,只需要包含它們兩個的主鍵就行了,作為GamePlayer的外鍵。而GamePlayer不再需要多余的主鍵了,可以用兩個外鍵做聯合主鍵。
先去Player類中添加一個導航屬性,因為是List類型,所以初始化一下,以免出現空指針空引用異常NullReferenceExcepiton:
public class Player
{
public Player()
{
GamePlayers = new List<GamePlayer>();
}
public int Id { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public List<GamePlayer> GamePlayers { get; set; }
}
Game類中也是一樣:
public class Game
{
public Game()
{
GamePlayers = new List<GamePlayer>();
}
public int Id { get; set; }
public int Round { get; set; }
public DateTimeOffset? StartTime { get; set; }
public List<GamePlayer> GamePlayers { get; set; }
}
現在在Game和Player類中都體現了一對多的關系,可以回GamePlayer類中再體現一下一對多的關系:
public class GamePlayer
{
public int GameId { get; set; }
public int PlayerId { get; set; }
public Game Game { get; set; }
public Player Player { get; set; }
}
這種一對多的關系可以兩端同時體現。然后必須手動設定聯合主鍵。可以去數據庫上下文DbContext中,重寫一下OnModelCreating方法,在里面使用Fluent API來設定:
public class DemoDbContext: DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=YangDemo;Integrated Security=True");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<GamePlayer>().HasKey(x => new
{
x.PlayerId,
x.GameId
});
}
public DbSet<League> Leagues { get; set; }
public DbSet<Club> Clubs { get; set; }
public DbSet<Player> Players { get; set; }
public DbSet<Game> Games { get; set; }
public DbSet<GamePlayer> GamePlayers { get; set; }
}
使用modelBuilder,選擇GamePlayer實體,使用HasKey方法來設定主鍵。聯合主鍵就是后面的匿名類中的兩個屬性。然后添加遷移並更新數據庫。
可以看到GamePlayers表中的聯合主鍵:
一對一的關系
假設每個隊員Player對應一份簡歷Resume,一份簡歷也只屬於一個隊員。
建立簡歷Resume類:
public class Resume
{
public int Id { get; set; }
public string Description { get; set; }
public int PlayerId { get; set; }
public Player Player { get; set; }
}
除了簡歷自己的屬性之外,需要建立一個與Player類的主鍵相同類型的屬性,在這里是int類型,並命名為PlayerId,然后建立到Player類的導航屬性。
在Player表中做同樣操作:
public int ResumeId { get; set; }
public Resume Resume { get; set; }
現在Player和Resume就是一對一的關系,EF Core會選擇其中的一個類作為主體,但是EF Core很可能選錯,所以還是要使用Fluent API來手動指定一下。
回到數據庫上下文中。這次Entity選擇哪一項都可以。但我們主體應該是Player,也就是Resume應該有一個外鍵是Player的Id。
modelBuilder.Entity<Resume>()
.HasOne(x => x.Player)
.WithOne(x => x.Resume)
.HasForeignKey<Resume>(x => x.PlayerId);
大意是Resume實體有一個Player,一個Player也有一個Resume,Resume有外鍵PlayerId。然后就可以添加遷移並更新數據庫了。