本文的代码基于.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(); 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();
}); 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 =
}; //插入Person表数据"李四"
Person personLiSi = new Person()
{
Name = "李四",
Age =
}; //插入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 > )
{
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是支持数据库表自己与自己多对多关系的实体类映射的,当实体类生成好后,其使用方法和普通的多对多关系差不多,没有太大的区别。

下载本文源代码

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

  1. 在ef core中使用postgres数据库的全文检索功能实战之中文支持

    前言 有关通用的postgres数据库全文检索在ef core中的使用方法,参见我的上一篇文章. 本文实践了zhparser中文插件进行全文检索. 准备工作 安装插件,最方便的方法是直接使用安装好插件 ...

  2. 在ef core中使用postgres数据库的全文检索功能实战

    起源 之前做的很多项目都使用solr/elasticsearch作为全文检索引擎,它们功能全面而强大,但是对于较小的项目而言,构建和维护成本显然过高,尤其是从关系数据库/文档数据库到全文检索引擎的数据 ...

  3. EntityFramework Core 2.x (ef core) 在迁移中自动生成数据库表和列说明

    在项目开发中有没有用过拼音首字母做列名或者接手这样的项目? 看见xmspsqb(项目审批申请表)这种表名时是否有一种无法抑制的想肛了取名的老兄的冲动? 更坑爹的是这种数据库没有文档(或者文档老旧不堪早 ...

  4. EF Core中如何正确地设置两张表之间的关联关系

    数据库 假设现在我们在SQL Server数据库中有下面两张表: Person表,代表的是一个人: CREATE TABLE [dbo].[Person]( ,) NOT NULL, ) NULL, ...

  5. EF Core中,通过实体类向SQL Server数据库表中插入数据后,实体对象是如何得到数据库表中的默认值的

    我们使用EF Core的实体类向SQL Server数据库表中插入数据后,如果数据库表中有自增列或默认值列,那么EF Core的实体对象也会返回插入到数据库表中的默认值. 下面我们通过例子来展示,EF ...

  6. EF Core 中多次从数据库查询实体数据,DbContext跟踪实体的情况

    使用EF Core时,如果多次从数据库中查询一个表的同一行数据,DbContext中跟踪(track)的实体到底有几个呢?我们下面就分情况讨论下. 数据库 首先我们的数据库中有一个Person表,其建 ...

  7. EF Core中怎么实现自动更新实体的属性值到数据库

    我们在开发系统的时候,经常会遇到这种需求数据库表中的行被更新时需要自动更新某些列. 数据库 比如下面的Person表有一列UpdateTime,这列数据要求在行被更新后自动更新为系统的当前时间. Pe ...

  8. EF Core中如何通过实体集合属性删除从表的数据

    假设在数据库中有两个表:Person表和Book表,Person和Book是一对多关系 Person表数据: Book表数据: 可以看到数据库Book表中所有的数据都属于Person表中"F ...

  9. 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获

    项目开发中的一些注意事项以及技巧总结   1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ...

随机推荐

  1. LeetCode——Employees Earning More Than Their Managers

    The Employee table holds all employees including their managers. Every employee has an Id, and there ...

  2. Flink使用SideOutPut替换Split实现分流

    以前的数据分析项目(版本1.4.2),对从Kafka读取的原始数据流,调用split接口实现分流. 新项目决定使用Flink 1.7.2,使用split接口进行分流的时候,发现接口被标记为deprac ...

  3. 基于记忆性的中值滤波O(r)与O(1)复杂度的算法实现

    本文参考博客:https://www.cnblogs.com/Imageshop/archive/2013/04/26/3045672.html 原生的中值滤波是基于排序算法的,这样的算法复杂度基本在 ...

  4. SQL注入:盲注

    盲注简介 所谓的盲注就是在服务器没有错误回显的时候完成的注入攻击. 服务器没有错误回显,对于攻击者来说缺少了非常重要的"调试信息". 盲注分类 1.布尔盲注 布尔很明显Ture和F ...

  5. CVE-2019-0708漏洞利用复现

    CVE-2019-0708漏洞利用复现 距离这个漏洞爆出来也有好几个月了,之前一直忙也没来得及写文档,现在重新做一遍. 准备环境: win7靶机 IP地址:172.16.15.118 Kali攻击机 ...

  6. ubuntu 用户名配置及磁盘挂载

    创建用户 我们创建的这个用户要放到 sudo 用户组,以便于我们可以执行一些需要 root 权限的操作. sudo useradd -m -s /bin/bash username sudo user ...

  7. Gmail Copilot是什么,有什么作用,好不好

    Gmail Copilot是一个Chrome浏览器插件,它构建在Gmail之上; 它自动列出你和联系人的所有电子邮件和对话信息: 它是一个小型CRM,可以让你查看联系人的个人信息快照,以及过去的任何邮 ...

  8. 201871010134-周英杰《面想对象程序设计(java)》第十一周学习总结

    项目 内容 <面向对象程序设计(java)> https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.com/ ...

  9. 201871020225-牟星源《面向对象程序设计(java)》第十一周学习总结

    201871020225-牟星源<面向对象程序设计(java)>第十一周学习总结 博文正文开头: 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu ...

  10. django学习-视图练习

    写一个真正有用的视图 每个视图必须要做的只有两件事: 返回一个包含被请求页面内容的HttpResponse对象,或抛出一个异常,比如Http404. 至于你还想干些什么,随便你. 你的视图可以从数据库 ...