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控制台項目中,寫了幾個方法來做測試:

  • ClearTables方法,用於清空Person表和FriendRelation表的數據
  • InsertPersonAndFriend方法,用於插入數據到Person表和FriendRelation表
  • ShowFriend方法,用於顯示Person表數據"張三"的朋友
  • DeleteFriend方法,用於刪除FriendRelation表數據

代碼如下所示:

using EFCoreSelfMany.Entities;
using System;
using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace EFCoreSelfMany
{
    class Program
    {
        //清空Person表和FriendRelation表的數據
        public static void ClearTables()
        {
            using (var dbContext = new DemoDBContext())
            {
                string sql = @"DELETE FROM [dbo].[FriendRelation];
                               DELETE FROM [dbo].[Person];";

                //注意在EF Core 3.0中ExecuteSqlCommand方法已經過時,請用下面的ExecuteSqlRaw方法替代
                dbContext.Database.ExecuteSqlRaw(sql);
            }
        }

        //插入數據到Person表和FriendRelation表
        public static void InsertPersonAndFriend()
        {
            using (var dbContext = new DemoDBContext())
            {
                //插入Person表數據"張三"
                Person personZhangSan = new Person()
                {
                    Name = "張三",
                    Age = 30
                };

                //插入Person表數據"李四"
                Person personLiSi = new Person()
                {
                    Name = "李四",
                    Age = 30
                };

                //插入FriendRelation表數據,設置"張三"和"李四"為朋友,注意"張三"是FriendRelation實體類的FromPersonNavigation屬性,"李四"是FriendRelation實體類的ToPersonNavigation屬性
                FriendRelation friendRelation = new FriendRelation()
                {
                    FromPersonNavigation = personZhangSan,
                    ToPersonNavigation = personLiSi
                };

                dbContext.Person.Add(personZhangSan);
                dbContext.Person.Add(personLiSi);
                dbContext.FriendRelation.Add(friendRelation);

                dbContext.SaveChanges();
            }

            Console.WriteLine("張三 和 李四 已經添加到數據庫");
        }

        //顯示Person表數據"張三"的朋友
        public static void ShowFriend()
        {
            using (var dbContext = new DemoDBContext())
            {
                //從數據庫Person表中找出"張三",並且使用EF Core的預加載(Eager Loading),通過Person實體類的FriendRelationFromPersonNavigation屬性查詢出FriendRelation表的數據,從而找出"張三"的朋友
                //注意,因為"張三"是通過FriendRelation實體類的FromPersonNavigation屬性添加到數據庫FriendRelation表的,所以這里使用EF Core的預加載(Eager Loading)方法Include時,要使用Person實體類的FriendRelationFromPersonNavigation屬性,最后通過FriendRelation實體類的ToPersonNavigation屬性從Person表中找出"李四"
                var personZhangSan = dbContext.Person.Where(p => p.Name == "張三").Include(p => p.FriendRelationFromPersonNavigation).ThenInclude(f => f.ToPersonNavigation).First();

                //判斷"張三"是否有朋友
                if (personZhangSan.FriendRelationFromPersonNavigation.Count > 0)
                {
                    Console.WriteLine($"{personZhangSan.Name} 的朋友是 {personZhangSan.FriendRelationFromPersonNavigation.First().ToPersonNavigation.Name}");
                }
                else
                {
                    Console.WriteLine($"{personZhangSan.Name} 沒有朋友");
                }
            }
        }

        //刪除FriendRelation表數據
        public static void DeleteFriend()
        {
            using (var dbContext = new DemoDBContext())
            {
                //從數據庫Person表中找出"張三",並且使用EF Core的預加載(Eager Loading),通過Person實體類的FriendRelationFromPersonNavigation屬性查詢出FriendRelation表的數據
                var personZhangSan = dbContext.Person.Where(p => p.Name == "張三").Include(p => p.FriendRelationFromPersonNavigation).First();
                var friendRelation = personZhangSan.FriendRelationFromPersonNavigation.First();

                //從FriendRelation表中刪除數據,也就是刪除"張三"和"李四"的朋友關系
                dbContext.FriendRelation.Remove(friendRelation);
                dbContext.SaveChanges();

                Console.WriteLine($"{personZhangSan.Name} 刪除了朋友");
            }
        }

        static void Main(string[] args)
        {
            ClearTables();

            InsertPersonAndFriend();

            ShowFriend();

            DeleteFriend();

            ShowFriend();

            Console.WriteLine("按任意鍵結束...");
            Console.ReadKey();
        }
    }
}

 

當代碼執行完Program類Main方法中的InsertPersonAndFriend方法后,EF Core后台生成的日志如下:

=============================== EF Core log started ===============================
Executed DbCommand (123ms) [Parameters=[@p0='?' (DbType = Int32), @p1='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [Person] ([Age], [Name])
VALUES (@p0, @p1);
SELECT [PersonID]
FROM [Person]
WHERE @@ROWCOUNT = 1 AND [PersonID] = scope_identity();
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Executed DbCommand (18ms) [Parameters=[@p0='?' (DbType = Int32), @p1='?' (Size = 50)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [Person] ([Age], [Name])
VALUES (@p0, @p1);
SELECT [PersonID]
FROM [Person]
WHERE @@ROWCOUNT = 1 AND [PersonID] = scope_identity();
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Executed DbCommand (19ms) [Parameters=[@p2='?' (DbType = Int32), @p3='?' (Size = 100), @p4='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [FriendRelation] ([FromPerson], [Remark], [ToPerson])
VALUES (@p2, @p3, @p4);
SELECT [FriendRelationID]
FROM [FriendRelation]
WHERE @@ROWCOUNT = 1 AND [FriendRelationID] = scope_identity();
=============================== EF Core log finished ===============================

可以看到InsertPersonAndFriend方法中,EF Core一共執行了三段SQL語句,前面兩段SQL就是在Person表中插入了"張三"和"李四"兩行數據,最后一段SQL就是在FriendRelation表中插入了"張三"和"李四"的朋友關系數據。

執行完Program類Main方法中的InsertPersonAndFriend方法后,數據庫Person表記錄如下:

數據庫FriendRelation表記錄如下:

控制台輸出結果如下:

 

當代碼執行完Program類Main方法中的第一個ShowFriend方法后,EF Core后台生成的日志如下:

=============================== EF Core log started ===============================
Executed DbCommand (13ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [t].[PersonID], [t].[Age], [t].[Name], [t0].[FriendRelationID], [t0].[FromPerson], [t0].[Remark], [t0].[ToPerson], [t0].[PersonID], [t0].[Age], [t0].[Name]
FROM (
    SELECT TOP(1) [p].[PersonID], [p].[Age], [p].[Name]
    FROM [Person] AS [p]
    WHERE ([p].[Name] = N'張三') AND [p].[Name] IS NOT NULL
) AS [t]
LEFT JOIN (
    SELECT [f].[FriendRelationID], [f].[FromPerson], [f].[Remark], [f].[ToPerson], [p0].[PersonID], [p0].[Age], [p0].[Name]
    FROM [FriendRelation] AS [f]
    LEFT JOIN [Person] AS [p0] ON [f].[ToPerson] = [p0].[PersonID]
) AS [t0] ON [t].[PersonID] = [t0].[FromPerson]
ORDER BY [t].[PersonID], [t0].[FriendRelationID]
=============================== EF Core log finished ===============================

可以看到EF Core生成了SQL語句,將"張三"和其朋友的數據都從Person表和FriendRelation表查詢出來了。

控制台輸出結果如下:

 

當代碼執行完Program類Main方法中的DeleteFriend方法后,EF Core后台生成的日志如下:

=============================== EF Core log started ===============================
Executed DbCommand (28ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [t].[PersonID], [t].[Age], [t].[Name], [f].[FriendRelationID], [f].[FromPerson], [f].[Remark], [f].[ToPerson]
FROM (
    SELECT TOP(1) [p].[PersonID], [p].[Age], [p].[Name]
    FROM [Person] AS [p]
    WHERE ([p].[Name] = N'張三') AND [p].[Name] IS NOT NULL
) AS [t]
LEFT JOIN [FriendRelation] AS [f] ON [t].[PersonID] = [f].[FromPerson]
ORDER BY [t].[PersonID], [f].[FriendRelationID]
=============================== EF Core log finished ===============================
=============================== EF Core log started ===============================
Executed DbCommand (15ms) [Parameters=[@p0='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
DELETE FROM [FriendRelation]
WHERE [FriendRelationID] = @p0;
SELECT @@ROWCOUNT;
=============================== EF Core log finished ===============================

可以看到EF Core生成了兩段SQL語句,第一段SQL是通過"張三"找出FriendRelation表的數據,第二段SQL是將找出的FriendRelation表數據進行了刪除。

執行完Program類Main方法中的DeleteFriend方法后,數據庫FriendRelation表記錄如下:

控制台輸出結果如下:

 

當代碼執行完Program類Main方法中的第二個ShowFriend方法后,控制台輸出結果如下:

 

所以我們可以看到,EF Core是支持數據庫表自己與自己多對多關系的實體類映射的,當實體類生成好后,其使用方法和普通的多對多關系差不多,沒有太大的區別。

 

下載本文源代碼

 


免責聲明!

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



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