EF Core中如何设置数据库表自己与自己的多对多关系

Posted opencoder

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EF Core中如何设置数据库表自己与自己的多对多关系相关的知识,希望对你有一定的参考价值。

本文的代码基于.NET Core 3.0和EF Core 3.0

 

有时候在数据库设计中,一个表自己会和自己是多对多关系。

 

在SQL Server数据库中,现在我们有Person表,代表一个人,建表语句如下:

CREATE TABLE [dbo].[Person](
    [PersonID] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NULL,
    [Age] [int] NULL,
 CONSTRAINT [PK_Person] PRIMARY KEY CLUSTERED 
(
    [PersonID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

其中PersonID列是Person表的主键。

 

因为一个人会有多个朋友,所以实际上这种人与人之间的朋友关系,是Person表自己和自己的多对多关系,所以我们还要建立一张FriendRelation表,来表示Person表自身的多对多关系,FriendRelation表的建表语句如下:

CREATE TABLE [dbo].[FriendRelation](
    [FriendRelationID] [int] IDENTITY(1,1) NOT NULL,
    [FromPerson] [int] NULL,
    [ToPerson] [int] NULL,
    [Remark] [nvarchar](100) NULL,
 CONSTRAINT [PK_FriendRelation] PRIMARY KEY CLUSTERED 
(
    [FriendRelationID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[FriendRelation]  WITH CHECK ADD  CONSTRAINT [FK_FriendRelation_Person_From] FOREIGN KEY([FromPerson])
REFERENCES [dbo].[Person] ([PersonID])
GO

ALTER TABLE [dbo].[FriendRelation] CHECK CONSTRAINT [FK_FriendRelation_Person_From]
GO

ALTER TABLE [dbo].[FriendRelation]  WITH CHECK ADD  CONSTRAINT [FK_FriendRelation_Person_To] FOREIGN KEY([ToPerson])
REFERENCES [dbo].[Person] ([PersonID])
GO

ALTER TABLE [dbo].[FriendRelation] CHECK CONSTRAINT [FK_FriendRelation_Person_To]
GO

其中FriendRelationID列是FriendRelation表的主键,我们可以看到在FriendRelation表中有两个外键关系:

  • 外键关系[FK_FriendRelation_Person_From],通过FriendRelation表的外键列[FromPerson],关联到Person表的主键列PersonID
  • 外键关系[FK_FriendRelation_Person_To],通过FriendRelation表的外键列[ToPerson],关联到Person表的主键列PersonID

因此Person表每行数据之间的多对多关系,就通过FriendRelation表的[FromPerson]列和[ToPerson]列建立起来了。

 

接下来,我们使用EF Core的DB First模式,通过Scaffold-DbContext指令,来生成实体类和DbContext类。

 

生成Person实体类如下:

using System;
using System.Collections.Generic;

namespace EFCoreSelfMany.Entities
{
    public partial class Person
    {
        public Person()
        {
            FriendRelationFromPersonNavigation = new HashSet<FriendRelation>();
            FriendRelationToPersonNavigation = new HashSet<FriendRelation>();
        }

        public int PersonId { get; set; }
        public string Name { get; set; }
        public int? Age { get; set; }

        public virtual ICollection<FriendRelation> FriendRelationFromPersonNavigation { get; set; }
        public virtual ICollection<FriendRelation> FriendRelationToPersonNavigation { get; set; }
    }
}

可以看到EF Core在实体类Person中生成了两个属性:

  • FriendRelationFromPersonNavigation属性,对应了FriendRelation表的外键列[FromPerson]
  • FriendRelationToPersonNavigation属性,对应了FriendRelation表的外键列[ToPerson]

所以通过这两个属性我们就能知道一个人有哪些朋友。

 

生成FriendRelation实体类如下:

using System;
using System.Collections.Generic;

namespace EFCoreSelfMany.Entities
{
    public partial class FriendRelation
    {
        public int FriendRelationId { get; set; }
        public int? FromPerson { get; set; }
        public int? ToPerson { get; set; }
        public string Remark { get; set; }

        public virtual Person FromPersonNavigation { get; set; }
        public virtual Person ToPersonNavigation { get; set; }
    }
}

可以看到EF Core在实体类FriendRelation中也生成了两个属性:

  • FromPersonNavigation属性,对应了FriendRelation表的外键列[FromPerson]
  • ToPersonNavigation属性,对应了FriendRelation表的外键列[ToPerson]

所以通过这两个属性,我们可以知道一个朋友关系中的两个人(Person表)到底是谁。

 

最后我们来看看,生成的DbContext类DemoDBContext:

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

namespace EFCoreSelfMany.Entities
{
    public partial class DemoDBContext : DbContext
    {
        public DemoDBContext()
        {
        }

        public DemoDBContext(DbContextOptions<DemoDBContext> options)
            : base(options)
        {
        }

        public virtual DbSet<FriendRelation> FriendRelation { get; set; }
        public virtual DbSet<Person> Person { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseSqlServer("Server=localhost;User Id=sa;Password=Dtt!123456;Database=DemoDB");

                optionsBuilder.UseLoggerFactory(new EFLoggerFactory());
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<FriendRelation>(entity =>
            {
                entity.Property(e => e.FriendRelationId).HasColumnName("FriendRelationID");

                entity.Property(e => e.Remark).HasMaxLength(100);

                entity.HasOne(d => d.FromPersonNavigation)
                    .WithMany(p => p.FriendRelationFromPersonNavigation)
                    .HasForeignKey(d => d.FromPerson)
                    .HasConstraintName("FK_FriendRelation_Person_From");

                entity.HasOne(d => d.ToPersonNavigation)
                    .WithMany(p => p.FriendRelationToPersonNavigation)
                    .HasForeignKey(d => d.ToPerson)
                    .HasConstraintName("FK_FriendRelation_Person_To");
            });

            modelBuilder.Entity<Person>(entity =>
            {
                entity.Property(e => e.PersonId).HasColumnName("PersonID");

                entity.Property(e => e.Name).HasMaxLength(50);
            });

            OnModelCreatingPartial(modelBuilder);
        }

        partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
    }
}

可以看到在实体类FriendRelation的Fluent API中(黄色高亮部分),设置了Person实体类自己与自己的多对多关系。

 

然后我们在.NET Core控制台项目中,写了几个方法来做测试:

 

以上是关于EF Core中如何设置数据库表自己与自己的多对多关系的主要内容,如果未能解决你的问题,请参考以下文章

定义引用同一个表的多对多关系(EF7/core)

EF Core中的多对多映射如何实现?

如何过滤 EF Core 中的多对多联接

EF Core 5.0 - 更新 ASP.NET Core Web API 中的多对多实体

使用流利的 api EF Core 5 的多对多关系

ef core中如何实现多对多的表映射关系