在数据库中表示这样的一个组织关系,可以使用自引用的方式实现,也就是主键和外键在同一张表中。
新建一个控制台应用程序
控制台项目结构:
项目引用的程序集:
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
1.在实体类中定义关系属性
OrgUnit.cs
using System.Collections.Generic;
namespace 自引用的组织结构树
{
class OrgUnit
{
/// <summary>
/// 主键
/// </summary>
public long Id { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 父节点
/// </summary>
public OrgUnit Parent { get; set; }
/// <summary>
/// 子节点
/// </summary>
public List<OrgUnit> Children { get; set; } = new List<OrgUnit>();
}
}
2.FluentAPI关系配置
OrgUnitConfig.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace 自引用的组织结构树
{
class OrgUnitConfig : IEntityTypeConfiguration<OrgUnit>
{
public void Configure(EntityTypeBuilder<OrgUnit> builder)
{
builder.ToTable("T_OrgUnits");
builder.Property(o => o.Name).IsUnicode().IsRequired().HasMaxLength(50);
builder.HasOne<OrgUnit>(o => o.Parent).WithMany(o => o.Children); //因为根节点没有父节点,所以不能定义为IsRequired
}
}
}
3.DbContext配置
用于定义数据库表和数据库链接等信息
MyDbContext.cs
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 自引用的组织结构树
{
class MyDbContext : DbContext
{
public DbSet<OrgUnit> orgUnits { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
string connStr = "Server=.;Database=EFCoreDemo2;Trusted_Connection=True;MultipleActiveResultSets=true";
optionsBuilder.UseSqlServer(connStr);
//打印log日志
//optionsBuilder.LogTo(msg =>
//{
// if (msg.Contains("CommandExecuting"))
// {
// Console.WriteLine(msg);
// }
// else
// {
// return;
// }
//});
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//从当前程序集加载所有IEntityTypeConfiguration
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
}
}
4.生成并执行数据库迁移脚本
使用命令生成数据库表创建脚本,并执行相关脚本。具体操作参考文章
操作完成后,数据库会新建一张表。
5.往数据库表插入记录
using System;
using System.Linq;
using System.Threading.Tasks;
namespace 自引用的组织结构树
{
class Program
{
static async Task Main(string[] args)
{
OrgUnit orgRoot = new OrgUnit { Name = "娃哈哈集团全球总部" };
OrgUnit orgAsia = new OrgUnit { Name = "娃哈哈集团亚洲总部" };
OrgUnit orgChina = new OrgUnit { Name = "娃哈哈集团中国总部" };
//指定对象的父节点
orgAsia.Parent = orgRoot;
orgChina.Parent = orgAsia;
OrgUnit orgEurope = new OrgUnit { Name = "娃哈哈集团欧洲总部" };
OrgUnit orgEngland = new OrgUnit { Name = "娃哈哈集团英国总部" };
orgEurope.Parent = orgRoot;
orgEngland.Parent = orgEurope;
using (MyDbContext ctx = new MyDbContext())
{
//将所有对象都添加到上下文的对象中
ctx.orgUnits.Add(orgRoot);
ctx.orgUnits.Add(orgAsia);
ctx.orgUnits.Add(orgChina);
ctx.orgUnits.Add(orgEurope);
ctx.orgUnits.Add(orgEngland);
//保存数据到数据库
await ctx.SaveChangesAsync();
}
}
}
}
程序运行后,查看数据库表记录:
数据已经入库,并且自动插入了父节点的ID。
6.查询实例
下面使用递归打印出所有节点的信息:
using System;
using System.Linq;
using System.Threading.Tasks;
namespace 自引用的组织结构树
{
class Program
{
static async Task Main(string[] args)
{
using (MyDbContext ctx = new MyDbContext())
{
OrgUnit orgRoor = ctx.orgUnits.Single(o => o.Parent == null); //查询根节点
Console.WriteLine(orgRoor.Name);
PrintChildren(1, ctx, orgRoor);
}
}
/// <summary>
/// 缩进打印parent所有的子节点
/// </summary>
/// <param name="indentLevel">打印时的缩进级别</param>
/// <param name="ctx">上下文</param>
/// <param name="parent">父节点</param>
static void PrintChildren(int indentLevel, MyDbContext ctx, OrgUnit parent)
{
var children = ctx.orgUnits.Where(o => o.Parent == parent);
foreach (var child in children)
{
Console.WriteLine(new String('\t', indentLevel) + child.Name);
PrintChildren(indentLevel + 1, ctx, child);//递归调用,打印以当前节点的子节点
}
}
}
}
执行结果:
该方法使用于自己引用自己的场景,比如员工信息表(员工和上级)等。