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。然后就可以添加迁移并更新数据库了。

posted @ 2020-06-25 18:59  Kit_L  阅读(1038)  评论(0编辑  收藏  举报