EntityFramework Core實現一對一、一對多、多對多關系


參考資料:
楊旭教程:https://www.bilibili.com/video/BV1xa4y1v7rR?p=4

准備工作

根據教程前幾節,已經建立好了三個實體類,並且生成了數據庫。三個實體類分別是:

聯賽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,在代碼中看不出,可以在數據庫中看一下:

UTOOLS1593075425872.png

確實存在。這種關系可以在代碼中指定,不指定也會自動生成。

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表中的聯合主鍵:

UTOOLS1593077807952.png

一對一的關系

假設每個隊員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。然后就可以添加遷移並更新數據庫了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM