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(); 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中如何设置数据库表自己与自己的多对多关系的更多相关文章
- 在ef core中使用postgres数据库的全文检索功能实战之中文支持
前言 有关通用的postgres数据库全文检索在ef core中的使用方法,参见我的上一篇文章. 本文实践了zhparser中文插件进行全文检索. 准备工作 安装插件,最方便的方法是直接使用安装好插件 ...
- 在ef core中使用postgres数据库的全文检索功能实战
起源 之前做的很多项目都使用solr/elasticsearch作为全文检索引擎,它们功能全面而强大,但是对于较小的项目而言,构建和维护成本显然过高,尤其是从关系数据库/文档数据库到全文检索引擎的数据 ...
- EntityFramework Core 2.x (ef core) 在迁移中自动生成数据库表和列说明
在项目开发中有没有用过拼音首字母做列名或者接手这样的项目? 看见xmspsqb(项目审批申请表)这种表名时是否有一种无法抑制的想肛了取名的老兄的冲动? 更坑爹的是这种数据库没有文档(或者文档老旧不堪早 ...
- EF Core中如何正确地设置两张表之间的关联关系
数据库 假设现在我们在SQL Server数据库中有下面两张表: Person表,代表的是一个人: CREATE TABLE [dbo].[Person]( ,) NOT NULL, ) NULL, ...
- EF Core中,通过实体类向SQL Server数据库表中插入数据后,实体对象是如何得到数据库表中的默认值的
我们使用EF Core的实体类向SQL Server数据库表中插入数据后,如果数据库表中有自增列或默认值列,那么EF Core的实体对象也会返回插入到数据库表中的默认值. 下面我们通过例子来展示,EF ...
- EF Core 中多次从数据库查询实体数据,DbContext跟踪实体的情况
使用EF Core时,如果多次从数据库中查询一个表的同一行数据,DbContext中跟踪(track)的实体到底有几个呢?我们下面就分情况讨论下. 数据库 首先我们的数据库中有一个Person表,其建 ...
- EF Core中怎么实现自动更新实体的属性值到数据库
我们在开发系统的时候,经常会遇到这种需求数据库表中的行被更新时需要自动更新某些列. 数据库 比如下面的Person表有一列UpdateTime,这列数据要求在行被更新后自动更新为系统的当前时间. Pe ...
- EF Core中如何通过实体集合属性删除从表的数据
假设在数据库中有两个表:Person表和Book表,Person和Book是一对多关系 Person表数据: Book表数据: 可以看到数据库Book表中所有的数据都属于Person表中"F ...
- 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获
项目开发中的一些注意事项以及技巧总结 1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ...
随机推荐
- 第1篇Scrum冲刺博客
目录 第1篇Scrum冲刺博客 各个成员在 Alpha 阶段认领的任务 各个成员的任务安排 整个项目预期的任务量 敏捷开发前的感想 团队期望 第1篇Scrum冲刺博客 各个成员在 Alpha 阶段认领 ...
- 构建Apache Web服务器
Apache 是世界使用排名第一的 Web 服务器软件.它可以运行在几乎所有广泛使用的计算机平台上,由于其跨平台和安全性被广泛使用,是最流行的 Web 服务器端软件之一.Apache工作模式有多种,其 ...
- LOJ 510: 「LibreOJ NOI Round #1」北校门外的回忆
题目传送门:LOJ #510. 题意简述: 给出一个在 \(K\) 进制下的树状数组,但是它的实现有问题. 形式化地说,令 \(\mathrm{lowbit}(x)\) 为在 \(K\) 进制下的 \ ...
- 201871010133-赵永军《面向对象程序设计(java)》第十五周学习总结
201871010133-赵永军<面向对象程序设计(java)>第十五周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ ...
- datagrid editor动态的改变不同行修改列的editor属性
onBeforeEdit: function (row) { let options = $(this).treegrid('options'); options.tempeditor = optio ...
- Cannot resolve reference to bean 'mongoTemplate' while setting bean property 'mongoOperations'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with na
问题: Springboot 启动时出错,报没法创建bean的错误,看到nested最后是关于mongoTemplate的错误. 过程: 看网上大多说的是修改mongoTemplate的配置,但是sp ...
- Java System.getProperty vs System.getenv
转自:https://www.baeldung.com/java-system-get-property-vs-system-getenv 1. Introduction The package ja ...
- leetcode组合总和 Ⅳ 解题路径
题目: 关于动态规划类题目的思路如何找在上一篇博客 https://www.cnblogs.com/niuyourou/p/11964842.html 讲的非常清楚了,该博客也成为了了leetcode ...
- 第四章、Go-面向“对象”
4.1.结构体和方法 (1)go语言的面向对象 go仅支持封装,不支持继承和多态 go语言没有class,只有struct (2)struct的创建 package main import " ...
- SPOJ31428 FIBONOMIAL(斐波那契数列)
神鱼推题,必是好题. 前几天刚做过[BJOI2019]勘破神机,于是就会这题了.(BJ人民强啊……%鱼) 首先要求是 $$\sum\limits_{i=0}^nx^if_i$$ 应该很明显能想到把 $ ...