惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
K
Kaspersky official blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Security Latest
Security Latest
P
Privacy & Cybersecurity Law Blog
Google DeepMind News
Google DeepMind News
Simon Willison's Weblog
Simon Willison's Weblog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
人人都是产品经理
人人都是产品经理
O
OpenAI News
Help Net Security
Help Net Security
N
News and Events Feed by Topic
博客园 - 司徒正美
U
Unit 42
Security Archives - TechRepublic
Security Archives - TechRepublic
The Cloudflare Blog
D
DataBreaches.Net
Y
Y Combinator Blog
AI
AI
L
LINUX DO - 最新话题
C
CXSECURITY Database RSS Feed - CXSecurity.com
H
Heimdal Security Blog
宝玉的分享
宝玉的分享
C
CERT Recently Published Vulnerability Notes
博客园 - 聂微东
TaoSecurity Blog
TaoSecurity Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
Project Zero
Project Zero
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Jina AI
Jina AI
M
MIT News - Artificial intelligence
Microsoft Azure Blog
Microsoft Azure Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Attack and Defense Labs
Attack and Defense Labs
V
V2EX
博客园 - 【当耐特】
S
SegmentFault 最新的问题
小众软件
小众软件
L
LangChain Blog
N
Netflix TechBlog - Medium
V
Vulnerabilities – Threatpost
T
Tor Project blog
AWS News Blog
AWS News Blog
博客园 - 三生石上(FineUI控件)
Recent Announcements
Recent Announcements
G
GRAHAM CLULEY
Know Your Adversary
Know Your Adversary
S
Securelist
T
Troy Hunt's Blog

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
第七章:多表联查与导航属性
我才是银古 · 2026-06-21 · via 博客园 - 我才是银古

第七章:多表联查与导航属性

目录

7.1 联表查询基础

7.1.1 Inner Join内连接

内连接返回两个表中匹配的记录:

public class InnerJoinExamples
{
    private readonly SqlSugarClient _db;

    public InnerJoinExamples(SqlSugarClient db)
    {
        _db = db;
    }

    // 基础内连接
    public List<UserDeptDto> BasicInnerJoin()
    {
        return _db.Queryable<User, Department>((u, d) => new JoinQueryInfos(
                JoinType.Inner, u.DepartmentId == d.Id
            ))
            .Select((u, d) => new UserDeptDto
            {
                UserId = u.Id,
                UserName = u.Name,
                UserEmail = u.Email,
                DepartmentName = d.Name,
                DepartmentCode = d.Code
            })
            .ToList();
    }

    // 使用InnerJoin方法
    public List<UserDeptDto> UseInnerJoinMethod()
    {
        return _db.Queryable<User>()
            .InnerJoin<Department>((u, d) => u.DepartmentId == d.Id)
            .Select((u, d) => new UserDeptDto
            {
                UserId = u.Id,
                UserName = u.Name,
                DepartmentName = d.Name
            })
            .ToList();
    }

    // 带条件的内连接
    public List<UserDeptDto> InnerJoinWithWhere(int minAge, int deptStatus)
    {
        return _db.Queryable<User>()
            .InnerJoin<Department>((u, d) => u.DepartmentId == d.Id)
            .Where((u, d) => u.Age >= minAge)
            .Where((u, d) => d.Status == deptStatus)
            .Select((u, d) => new UserDeptDto
            {
                UserId = u.Id,
                UserName = u.Name,
                UserAge = u.Age,
                DepartmentName = d.Name
            })
            .ToList();
    }

    // 内连接后分组统计
    public List<object> InnerJoinWithGroup()
    {
        return _db.Queryable<User>()
            .InnerJoin<Department>((u, d) => u.DepartmentId == d.Id)
            .GroupBy((u, d) => d.Name)
            .Select((u, d) => new
            {
                DepartmentName = d.Name,
                UserCount = SqlFunc.AggregateCount(u.Id),
                AvgAge = SqlFunc.AggregateAvg(u.Age)
            })
            .ToList<object>();
    }
}

public class UserDeptDto
{
    public int UserId { get; set; }
    public string UserName { get; set; }
    public string UserEmail { get; set; }
    public int UserAge { get; set; }
    public string DepartmentName { get; set; }
    public string DepartmentCode { get; set; }
}

7.1.2 Left Join左连接

左连接返回左表所有记录及右表匹配记录:

public class LeftJoinExamples
{
    private readonly SqlSugarClient _db;

    public LeftJoinExamples(SqlSugarClient db)
    {
        _db = db;
    }

    // 基础左连接
    public List<UserDeptDto> BasicLeftJoin()
    {
        return _db.Queryable<User>()
            .LeftJoin<Department>((u, d) => u.DepartmentId == d.Id)
            .Select((u, d) => new UserDeptDto
            {
                UserId = u.Id,
                UserName = u.Name,
                DepartmentName = d.Name ?? "未分配部门"
            })
            .ToList();
    }

    // 查询未分配部门的用户
    public List<User> GetUsersWithoutDepartment()
    {
        return _db.Queryable<User>()
            .LeftJoin<Department>((u, d) => u.DepartmentId == d.Id)
            .Where((u, d) => d.Id == null)
            .Select((u, d) => u)
            .ToList();
    }

    // 左连接多表
    public List<object> LeftJoinMultipleTables()
    {
        return _db.Queryable<User>()
            .LeftJoin<Department>((u, d) => u.DepartmentId == d.Id)
            .LeftJoin<Role>((u, d, r) => u.RoleId == r.Id)
            .Select((u, d, r) => new
            {
                UserId = u.Id,
                UserName = u.Name,
                DepartmentName = d.Name ?? "未分配",
                RoleName = r.Name ?? "无角色"
            })
            .ToList<object>();
    }

    // 左连接统计
    public List<object> LeftJoinWithStats()
    {
        return _db.Queryable<Department>()
            .LeftJoin<User>((d, u) => d.Id == u.DepartmentId)
            .GroupBy((d, u) => new { d.Id, d.Name })
            .Select((d, u) => new
            {
                DepartmentId = d.Id,
                DepartmentName = d.Name,
                UserCount = SqlFunc.AggregateCount(u.Id)
            })
            .ToList<object>();
    }
}

7.1.3 Right Join右连接

右连接返回右表所有记录及左表匹配记录:

public class RightJoinExamples
{
    private readonly SqlSugarClient _db;

    public RightJoinExamples(SqlSugarClient db)
    {
        _db = db;
    }

    // 基础右连接
    public List<object> BasicRightJoin()
    {
        return _db.Queryable<User>()
            .RightJoin<Department>((u, d) => u.DepartmentId == d.Id)
            .Select((u, d) => new
            {
                UserId = u.Id,
                UserName = u.Name,
                DepartmentId = d.Id,
                DepartmentName = d.Name
            })
            .ToList<object>();
    }

    // 查询没有员工的部门
    public List<Department> GetDepartmentsWithoutUsers()
    {
        return _db.Queryable<User>()
            .RightJoin<Department>((u, d) => u.DepartmentId == d.Id)
            .Where((u, d) => u.Id == null)
            .Select((u, d) => d)
            .ToList();
    }
}

7.1.4 Full Join全连接

全连接返回两个表的所有记录:

public class FullJoinExamples
{
    private readonly SqlSugarClient _db;

    public FullJoinExamples(SqlSugarClient db)
    {
        _db = db;
    }

    // 全连接(MySQL不直接支持,需要用Union实现)
    public List<object> FullJoinSimulation()
    {
        var leftJoin = _db.Queryable<User>()
            .LeftJoin<Department>((u, d) => u.DepartmentId == d.Id)
            .Select((u, d) => new
            {
                UserId = u.Id,
                UserName = u.Name,
                DepartmentId = d.Id,
                DepartmentName = d.Name
            });

        var rightJoin = _db.Queryable<User>()
            .RightJoin<Department>((u, d) => u.DepartmentId == d.Id)
            .Select((u, d) => new
            {
                UserId = u.Id,
                UserName = u.Name,
                DepartmentId = d.Id,
                DepartmentName = d.Name
            });

        return _db.Union(leftJoin, rightJoin).ToList<object>();
    }
}

7.2 导航属性

7.2.1 导航属性定义

定义实体类之间的导航关系:

// 用户实体
public class User
{
    [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public int DepartmentId { get; set; }
    public int RoleId { get; set; }

    // 导航属性 - 一对一
    [Navigate(NavigateType.OneToOne, nameof(Id))]
    public UserProfile Profile { get; set; }

    // 导航属性 - 多对一
    [Navigate(NavigateType.ManyToOne, nameof(DepartmentId))]
    public Department Department { get; set; }

    [Navigate(NavigateType.ManyToOne, nameof(RoleId))]
    public Role Role { get; set; }

    // 导航属性 - 一对多
    [Navigate(NavigateType.OneToMany, nameof(Order.UserId))]
    public List<Order> Orders { get; set; }

    // 导航属性 - 多对多
    [Navigate(typeof(UserRole), nameof(UserRole.UserId), nameof(UserRole.RoleId))]
    public List<Role> Roles { get; set; }
}

// 部门实体
public class Department
{
    [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
    public int Id { get; set; }
    public string Name { get; set; }
    public string Code { get; set; }

    // 导航属性 - 一对多
    [Navigate(NavigateType.OneToMany, nameof(User.DepartmentId))]
    public List<User> Users { get; set; }
}

// 订单实体
public class Order
{
    [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
    public int Id { get; set; }
    public string OrderNo { get; set; }
    public int UserId { get; set; }
    public decimal Amount { get; set; }

    // 导航属性 - 多对一
    [Navigate(NavigateType.ManyToOne, nameof(UserId))]
    public User User { get; set; }

    // 导航属性 - 一对多
    [Navigate(NavigateType.OneToMany, nameof(OrderDetail.OrderId))]
    public List<OrderDetail> Details { get; set; }
}

// 用户资料实体
public class UserProfile
{
    [SugarColumn(IsPrimaryKey = true)]
    public int UserId { get; set; }
    public string Avatar { get; set; }
    public string Bio { get; set; }
    public DateTime Birthday { get; set; }

    // 导航属性 - 一对一
    [Navigate(NavigateType.OneToOne, nameof(UserId))]
    public User User { get; set; }
}

7.2.2 Mapper映射

使用Mapper自动填充导航属性:

public class MapperExamples
{
    private readonly SqlSugarClient _db;

    public MapperExamples(SqlSugarClient db)
    {
        _db = db;
    }

    // 基础Mapper使用
    public List<User> BasicMapper()
    {
        return _db.Queryable<User>()
            .Mapper(u => u.Department, u => u.DepartmentId)
            .ToList();
    }

    // 映射多个导航属性
    public List<User> MapperMultiple()
    {
        return _db.Queryable<User>()
            .Mapper(u => u.Department, u => u.DepartmentId)
            .Mapper(u => u.Role, u => u.RoleId)
            .Mapper(u => u.Profile, u => u.Id)
            .ToList();
    }

    // 映射一对多关系
    public List<User> MapperOneToMany()
    {
        return _db.Queryable<User>()
            .Mapper(u => u.Orders, u => u.Orders.First().UserId)
            .ToList();
    }

    // 条件映射
    public List<User> MapperWithCondition()
    {
        return _db.Queryable<User>()
            .Mapper(u => u.Orders,
                u => u.Orders.First().UserId,
                o => o.Amount > 1000)  // 只映射金额大于1000的订单
            .ToList();
    }

    // 映射并排序
    public List<User> MapperWithOrder()
    {
        return _db.Queryable<User>()
            .Mapper(u => u.Orders,
                u => u.Orders.First().UserId,
                null,
                o => o.OrderBy(x => x.CreateTime, OrderByType.Desc))
            .ToList();
    }
}

7.2.3 Includes加载

使用Includes预加载关联数据:

public class IncludesExamples
{
    private readonly SqlSugarClient _db;

    public IncludesExamples(SqlSugarClient db)
    {
        _db = db;
    }

    // 基础Includes
    public List<User> BasicIncludes()
    {
        return _db.Queryable<User>()
            .Includes(u => u.Department)
            .ToList();
    }

    // 多级Includes
    public List<User> MultiLevelIncludes()
    {
        return _db.Queryable<User>()
            .Includes(u => u.Department)
            .Includes(u => u.Orders, o => o.Details)  // 包含订单及订单详情
            .ToList();
    }

    // 条件Includes
    public List<User> IncludesWithCondition()
    {
        return _db.Queryable<User>()
            .Includes(u => u.Orders.Where(o => o.Amount > 1000).ToList())
            .ToList();
    }

    // Includes多个导航属性
    public List<User> IncludesMultiple()
    {
        return _db.Queryable<User>()
            .Includes(u => u.Department)
            .Includes(u => u.Role)
            .Includes(u => u.Profile)
            .Includes(u => u.Orders)
            .ToList();
    }

    // 深层级Includes
    public List<Department> DeepIncludes()
    {
        return _db.Queryable<Department>()
            .Includes(d => d.Users, u => u.Orders, o => o.Details)
            .ToList();
    }
}

7.3 一对一关系

7.3.1 配置一对一

配置一对一关系:

public class OneToOneConfiguration
{
    // 主表 - User
    public class User
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }

        // 一对一导航属性
        [Navigate(NavigateType.OneToOne, nameof(Id))]
        public UserProfile Profile { get; set; }
    }

    // 从表 - UserProfile
    public class UserProfile
    {
        [SugarColumn(IsPrimaryKey = true)]
        public int UserId { get; set; }
        public string Avatar { get; set; }
        public string Bio { get; set; }
        public DateTime Birthday { get; set; }

        // 反向导航属性
        [Navigate(NavigateType.OneToOne, nameof(UserId))]
        public User User { get; set; }
    }
}

7.3.2 查询一对一

查询一对一关系数据:

public class OneToOneQuery
{
    private readonly SqlSugarClient _db;

    public OneToOneQuery(SqlSugarClient db)
    {
        _db = db;
    }

    // 使用Join查询
    public List<object> QueryWithJoin()
    {
        return _db.Queryable<User>()
            .LeftJoin<UserProfile>((u, p) => u.Id == p.UserId)
            .Select((u, p) => new
            {
                UserId = u.Id,
                UserName = u.Name,
                Avatar = p.Avatar,
                Bio = p.Bio
            })
            .ToList<object>();
    }

    // 使用Mapper查询
    public List<User> QueryWithMapper()
    {
        return _db.Queryable<User>()
            .Mapper(u => u.Profile, u => u.Id)
            .ToList();
    }

    // 使用Includes查询
    public List<User> QueryWithIncludes()
    {
        return _db.Queryable<User>()
            .Includes(u => u.Profile)
            .ToList();
    }

    // 反向查询
    public List<UserProfile> QueryProfileWithUser()
    {
        return _db.Queryable<UserProfile>()
            .Includes(p => p.User)
            .ToList();
    }
}

7.3.3 级联操作

一对一关系的级联操作:

public class OneToOneCascade
{
    private readonly SqlSugarClient _db;

    public OneToOneCascade(SqlSugarClient db)
    {
        _db = db;
    }

    // 级联插入
    public bool InsertWithProfile(User user, UserProfile profile)
    {
        try
        {
            _db.Ado.BeginTran();

            // 插入用户
            var userId = _db.Insertable(user).ExecuteReturnIdentity();

            // 插入用户资料
            profile.UserId = userId;
            _db.Insertable(profile).ExecuteCommand();

            _db.Ado.CommitTran();
            return true;
        }
        catch
        {
            _db.Ado.RollbackTran();
            throw;
        }
    }

    // 级联更新
    public bool UpdateWithProfile(User user, UserProfile profile)
    {
        try
        {
            _db.Ado.BeginTran();

            _db.Updateable(user).ExecuteCommand();
            _db.Updateable(profile).ExecuteCommand();

            _db.Ado.CommitTran();
            return true;
        }
        catch
        {
            _db.Ado.RollbackTran();
            throw;
        }
    }

    // 级联删除
    public bool DeleteWithProfile(int userId)
    {
        try
        {
            _db.Ado.BeginTran();

            _db.Deleteable<UserProfile>().Where(p => p.UserId == userId).ExecuteCommand();
            _db.Deleteable<User>().In(userId).ExecuteCommand();

            _db.Ado.CommitTran();
            return true;
        }
        catch
        {
            _db.Ado.RollbackTran();
            throw;
        }
    }
}

7.4 一对多关系

7.4.1 配置一对多

配置一对多关系:

public class OneToManyConfiguration
{
    // 主表 - Department
    public class Department
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public string Name { get; set; }

        // 一对多导航属性
        [Navigate(NavigateType.OneToMany, nameof(User.DepartmentId))]
        public List<User> Users { get; set; }
    }

    // 从表 - User
    public class User
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public string Name { get; set; }
        public int DepartmentId { get; set; }

        // 多对一导航属性
        [Navigate(NavigateType.ManyToOne, nameof(DepartmentId))]
        public Department Department { get; set; }
    }
}

7.4.2 查询一对多

查询一对多关系数据:

public class OneToManyQuery
{
    private readonly SqlSugarClient _db;

    public OneToManyQuery(SqlSugarClient db)
    {
        _db = db;
    }

    // 使用Mapper查询
    public List<Department> QueryWithMapper()
    {
        return _db.Queryable<Department>()
            .Mapper(d => d.Users, d => d.Users.First().DepartmentId)
            .ToList();
    }

    // 使用Includes查询
    public List<Department> QueryWithIncludes()
    {
        return _db.Queryable<Department>()
            .Includes(d => d.Users)
            .ToList();
    }

    // 条件过滤子集合
    public List<Department> QueryWithCondition()
    {
        return _db.Queryable<Department>()
            .Mapper(d => d.Users,
                d => d.Users.First().DepartmentId,
                u => u.Age >= 18)  // 只加载成年用户
            .ToList();
    }

    // 子集合排序
    public List<Department> QueryWithOrder()
    {
        return _db.Queryable<Department>()
            .Mapper(d => d.Users,
                d => d.Users.First().DepartmentId,
                null,
                u => u.OrderBy(x => x.Name))
            .ToList();
    }

    // 反向查询(多对一)
    public List<User> QueryUserWithDepartment()
    {
        return _db.Queryable<User>()
            .Includes(u => u.Department)
            .ToList();
    }
}

7.4.3 分页加载

对一对多关系进行分页加载:

public class OneToManyPagination
{
    private readonly SqlSugarClient _db;

    public OneToManyPagination(SqlSugarClient db)
    {
        _db = db;
    }

    // 子集合分页
    public List<Department> QueryWithPagination(int pageIndex, int pageSize)
    {
        var departments = _db.Queryable<Department>().ToList();

        foreach (var dept in departments)
        {
            dept.Users = _db.Queryable<User>()
                .Where(u => u.DepartmentId == dept.Id)
                .ToPageList(pageIndex, pageSize);
        }

        return departments;
    }

    // 优化版本 - 减少查询次数
    public List<Department> QueryWithOptimizedPagination(int pageIndex, int pageSize)
    {
        // 先查询部门
        var departments = _db.Queryable<Department>().ToList();
        var deptIds = departments.Select(d => d.Id).ToList();

        // 批量查询所有用户
        var allUsers = _db.Queryable<User>()
            .Where(u => deptIds.Contains(u.DepartmentId))
            .ToList();

        // 在内存中分配用户到部门
        foreach (var dept in departments)
        {
            dept.Users = allUsers
                .Where(u => u.DepartmentId == dept.Id)
                .Skip((pageIndex - 1) * pageSize)
                .Take(pageSize)
                .ToList();
        }

        return departments;
    }
}

7.5 多对多关系

7.5.1 配置多对多

配置多对多关系:

public class ManyToManyConfiguration
{
    // 学生表
    public class Student
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public string Name { get; set; }

        // 多对多导航属性
        [Navigate(typeof(StudentCourse), nameof(StudentCourse.StudentId), nameof(StudentCourse.CourseId))]
        public List<Course> Courses { get; set; }
    }

    // 课程表
    public class Course
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public string Name { get; set; }
        public int Credits { get; set; }

        // 多对多导航属性
        [Navigate(typeof(StudentCourse), nameof(StudentCourse.CourseId), nameof(StudentCourse.StudentId))]
        public List<Student> Students { get; set; }
    }

    // 中间表
    public class StudentCourse
    {
        [SugarColumn(IsPrimaryKey = true)]
        public int StudentId { get; set; }

        [SugarColumn(IsPrimaryKey = true)]
        public int CourseId { get; set; }

        public DateTime EnrollDate { get; set; }
        public decimal? Score { get; set; }
    }
}

7.5.2 查询多对多

查询多对多关系数据:

public class ManyToManyQuery
{
    private readonly SqlSugarClient _db;

    public ManyToManyQuery(SqlSugarClient db)
    {
        _db = db;
    }

    // 使用Mapper查询
    public List<Student> QueryStudentsWithCourses()
    {
        return _db.Queryable<Student>()
            .Mapper(s => s.Courses,
                s => s.Courses.First().Id,
                c => c.Id)
            .ToList();
    }

    // 使用Includes查询
    public List<Student> QueryWithIncludes()
    {
        return _db.Queryable<Student>()
            .Includes(s => s.Courses)
            .ToList();
    }

    // 反向查询
    public List<Course> QueryCoursesWithStudents()
    {
        return _db.Queryable<Course>()
            .Includes(c => c.Students)
            .ToList();
    }

    // 使用Join查询
    public List<object> QueryWithJoin()
    {
        return _db.Queryable<Student, StudentCourse, Course>(
                (s, sc, c) => new JoinQueryInfos(
                    JoinType.Inner, s.Id == sc.StudentId,
                    JoinType.Inner, sc.CourseId == c.Id
                ))
            .Select((s, sc, c) => new
            {
                StudentId = s.Id,
                StudentName = s.Name,
                CourseId = c.Id,
                CourseName = c.Name,
                EnrollDate = sc.EnrollDate,
                Score = sc.Score
            })
            .ToList<object>();
    }

    // 查询学生的课程数量
    public List<object> CountStudentCourses()
    {
        return _db.Queryable<Student, StudentCourse>(
                (s, sc) => s.Id == sc.StudentId)
            .GroupBy((s, sc) => new { s.Id, s.Name })
            .Select((s, sc) => new
            {
                StudentId = s.Id,
                StudentName = s.Name,
                CourseCount = SqlFunc.AggregateCount(sc.CourseId)
            })
            .ToList<object>();
    }
}

7.5.3 中间表操作

操作多对多关系的中间表:

public class ManyToManyOperations
{
    private readonly SqlSugarClient _db;

    public ManyToManyOperations(SqlSugarClient db)
    {
        _db = db;
    }

    // 添加关系
    public bool AddStudentCourse(int studentId, int courseId, DateTime enrollDate)
    {
        var sc = new StudentCourse
        {
            StudentId = studentId,
            CourseId = courseId,
            EnrollDate = enrollDate
        };

        return _db.Insertable(sc).ExecuteCommand() > 0;
    }

    // 批量添加关系
    public bool AddStudentCourses(int studentId, List<int> courseIds)
    {
        var list = courseIds.Select(cid => new StudentCourse
        {
            StudentId = studentId,
            CourseId = cid,
            EnrollDate = DateTime.Now
        }).ToList();

        return _db.Insertable(list).ExecuteCommand() > 0;
    }

    // 删除关系
    public bool RemoveStudentCourse(int studentId, int courseId)
    {
        return _db.Deleteable<StudentCourse>()
            .Where(sc => sc.StudentId == studentId && sc.CourseId == courseId)
            .ExecuteCommand() > 0;
    }

    // 更新中间表数据
    public bool UpdateScore(int studentId, int courseId, decimal score)
    {
        return _db.Updateable<StudentCourse>()
            .SetColumns(sc => sc.Score == score)
            .Where(sc => sc.StudentId == studentId && sc.CourseId == courseId)
            .ExecuteCommand() > 0;
    }

    // 查询学生的所有课程关系
    public List<StudentCourse> GetStudentCourses(int studentId)
    {
        return _db.Queryable<StudentCourse>()
            .Where(sc => sc.StudentId == studentId)
            .ToList();
    }
}

7.6 复杂多表查询

7.6.1 三表联查

进行三表联合查询:

public class ThreeTableJoin
{
    private readonly SqlSugarClient _db;

    public ThreeTableJoin(SqlSugarClient db)
    {
        _db = db;
    }

    // 基础三表联查
    public List<OrderFullDto> BasicThreeTableJoin()
    {
        return _db.Queryable<Order, User, Product>(
                (o, u, p) => new JoinQueryInfos(
                    JoinType.Left, o.UserId == u.Id,
                    JoinType.Left, o.ProductId == p.Id
                ))
            .Select((o, u, p) => new OrderFullDto
            {
                OrderId = o.Id,
                OrderNo = o.OrderNo,
                OrderTime = o.CreateTime,
                UserName = u.Name,
                UserEmail = u.Email,
                ProductName = p.Name,
                ProductPrice = p.Price,
                Quantity = o.Quantity,
                TotalAmount = o.Quantity * p.Price
            })
            .ToList();
    }

    // 三表联查带条件
    public List<OrderFullDto> ThreeTableJoinWithCondition(DateTime startDate, DateTime endDate)
    {
        return _db.Queryable<Order, User, Product>(
                (o, u, p) => new JoinQueryInfos(
                    JoinType.Left, o.UserId == u.Id,
                    JoinType.Left, o.ProductId == p.Id
                ))
            .Where((o, u, p) => o.CreateTime >= startDate && o.CreateTime < endDate)
            .Where((o, u, p) => o.Status == 1)
            .Select((o, u, p) => new OrderFullDto
            {
                OrderId = o.Id,
                OrderNo = o.OrderNo,
                UserName = u.Name,
                ProductName = p.Name,
                TotalAmount = o.Quantity * p.Price
            })
            .ToList();
    }

    // 三表联查分组统计
    public List<object> ThreeTableJoinWithGroup()
    {
        return _db.Queryable<Order, User, Product>(
                (o, u, p) => new JoinQueryInfos(
                    JoinType.Left, o.UserId == u.Id,
                    JoinType.Left, o.ProductId == p.Id
                ))
            .GroupBy((o, u, p) => new { u.Id, u.Name })
            .Select((o, u, p) => new
            {
                UserId = u.Id,
                UserName = u.Name,
                OrderCount = SqlFunc.AggregateCount(o.Id),
                TotalAmount = SqlFunc.AggregateSum(o.Quantity * p.Price)
            })
            .ToList<object>();
    }
}

public class OrderFullDto
{
    public int OrderId { get; set; }
    public string OrderNo { get; set; }
    public DateTime OrderTime { get; set; }
    public string UserName { get; set; }
    public string UserEmail { get; set; }
    public string ProductName { get; set; }
    public decimal ProductPrice { get; set; }
    public int Quantity { get; set; }
    public decimal TotalAmount { get; set; }
}

7.6.2 四表及以上联查

进行更复杂的多表联查:

public class MultiTableJoin
{
    private readonly SqlSugarClient _db;

    public MultiTableJoin(SqlSugarClient db)
    {
        _db = db;
    }

    // 四表联查
    public List<object> FourTableJoin()
    {
        return _db.Queryable<Order, User, Product, Category>(
                (o, u, p, c) => new JoinQueryInfos(
                    JoinType.Left, o.UserId == u.Id,
                    JoinType.Left, o.ProductId == p.Id,
                    JoinType.Left, p.CategoryId == c.Id
                ))
            .Select((o, u, p, c) => new
            {
                OrderNo = o.OrderNo,
                UserName = u.Name,
                ProductName = p.Name,
                CategoryName = c.Name,
                TotalAmount = o.Quantity * p.Price
            })
            .ToList<object>();
    }

    // 五表联查
    public List<object> FiveTableJoin()
    {
        return _db.Queryable<Order, User, Department, Product, Category>(
                (o, u, d, p, c) => new JoinQueryInfos(
                    JoinType.Left, o.UserId == u.Id,
                    JoinType.Left, u.DepartmentId == d.Id,
                    JoinType.Left, o.ProductId == p.Id,
                    JoinType.Left, p.CategoryId == c.Id
                ))
            .Select((o, u, d, p, c) => new
            {
                OrderNo = o.OrderNo,
                UserName = u.Name,
                DepartmentName = d.Name,
                ProductName = p.Name,
                CategoryName = c.Name,
                TotalAmount = o.Quantity * p.Price
            })
            .ToList<object>();
    }

    // 复杂多表联查with分组
    public List<object> ComplexMultiTableJoin()
    {
        return _db.Queryable<Order, User, Department, Product>(
                (o, u, d, p) => new JoinQueryInfos(
                    JoinType.Left, o.UserId == u.Id,
                    JoinType.Left, u.DepartmentId == d.Id,
                    JoinType.Left, o.ProductId == p.Id
                ))
            .GroupBy((o, u, d, p) => new { d.Id, d.Name, Year = o.CreateTime.Year, Month = o.CreateTime.Month })
            .Select((o, u, d, p) => new
            {
                DepartmentId = d.Id,
                DepartmentName = d.Name,
                Year = o.CreateTime.Year,
                Month = o.CreateTime.Month,
                OrderCount = SqlFunc.AggregateCount(o.Id),
                TotalAmount = SqlFunc.AggregateSum(o.Quantity * p.Price),
                UserCount = SqlFunc.AggregateCountDistinct(u.Id)
            })
            .ToList<object>();
    }
}

7.6.3 自连接查询

查询同一张表的层级关系:

public class SelfJoinQuery
{
    private readonly SqlSugarClient _db;

    public SelfJoinQuery(SqlSugarClient db)
    {
        _db = db;
    }

    // 查询部门及其父部门
    public List<object> QueryDepartmentWithParent()
    {
        return _db.Queryable<Department, Department>(
                (d1, d2) => new JoinQueryInfos(
                    JoinType.Left, d1.ParentId == d2.Id
                ))
            .Select((d1, d2) => new
            {
                DeptId = d1.Id,
                DeptName = d1.Name,
                ParentId = d2.Id,
                ParentName = d2.Name
            })
            .ToList<object>();
    }

    // 查询所有子部门
    public List<Department> QueryChildDepartments(int parentId)
    {
        return _db.Queryable<Department>()
            .Where(d => d.ParentId == parentId)
            .ToList();
    }

    // 递归查询所有子部门
    public List<Department> QueryAllChildDepartments(int parentId)
    {
        var result = new List<Department>();
        var queue = new Queue<int>();
        queue.Enqueue(parentId);

        while (queue.Count > 0)
        {
            var currentId = queue.Dequeue();
            var children = _db.Queryable<Department>()
                .Where(d => d.ParentId == currentId)
                .ToList();

            result.AddRange(children);
            foreach (var child in children)
            {
                queue.Enqueue(child.Id);
            }
        }

        return result;
    }
}

public class Department
{
    [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
    public int Id { get; set; }
    public string Name { get; set; }
    public int? ParentId { get; set; }
}

7.7 性能优化

public class JoinPerformanceOptimization
{
    private readonly SqlSugarClient _db;

    public JoinPerformanceOptimization(SqlSugarClient db)
    {
        _db = db;
    }

    // 1. 避免N+1查询
    public List<Department> AvoidNPlusOne()
    {
        // 错误方式 - N+1查询
        // var depts = _db.Queryable<Department>().ToList();
        // foreach(var dept in depts) {
        //     dept.Users = _db.Queryable<User>().Where(u => u.DepartmentId == dept.Id).ToList();
        // }

        // 正确方式 - 使用Mapper或Includes
        return _db.Queryable<Department>()
            .Mapper(d => d.Users, d => d.Users.First().DepartmentId)
            .ToList();
    }

    // 2. 只查询需要的字段
    public List<object> SelectOnlyNeededFields()
    {
        return _db.Queryable<User, Department>((u, d) => u.DepartmentId == d.Id)
            .Select((u, d) => new
            {
                u.Id,
                u.Name,
                DepartmentName = d.Name
            })
            .ToList<object>();
    }

    // 3. 使用分页减少数据量
    public PagedResult<object> UsePagedQuery(int pageIndex, int pageSize)
    {
        int totalCount = 0;
        var data = _db.Queryable<User, Department>((u, d) => u.DepartmentId == d.Id)
            .Select((u, d) => new
            {
                u.Id,
                u.Name,
                DepartmentName = d.Name
            })
            .ToPageList(pageIndex, pageSize, ref totalCount);

        return new PagedResult<object>
        {
            Items = data,
            TotalCount = totalCount,
            PageIndex = pageIndex,
            PageSize = pageSize
        };
    }

    // 4. 使用索引优化Join
    public List<object> UseIndexedJoin()
    {
        // 确保连接字段有索引
        // CREATE INDEX idx_user_deptid ON User(DepartmentId);
        // CREATE INDEX idx_order_userid ON Order(UserId);

        return _db.Queryable<User, Department, Order>(
                (u, d, o) => new JoinQueryInfos(
                    JoinType.Left, u.DepartmentId == d.Id,
                    JoinType.Left, o.UserId == u.Id
                ))
            .Select((u, d, o) => new
            {
                u.Name,
                DepartmentName = d.Name,
                OrderCount = SqlFunc.AggregateCount(o.Id)
            })
            .ToList<object>();
    }

    // 5. 批量加载导航属性
    public List<Department> BatchLoadNavigations()
    {
        var depts = _db.Queryable<Department>().ToList();
        var deptIds = depts.Select(d => d.Id).ToList();

        // 批量查询所有用户
        var users = _db.Queryable<User>()
            .Where(u => deptIds.Contains(u.DepartmentId))
            .ToList();

        // 在内存中分配
        foreach (var dept in depts)
        {
            dept.Users = users.Where(u => u.DepartmentId == dept.Id).ToList();
        }

        return depts;
    }
}

7.8 最佳实践

public class MultiTableBestPractices
{
    private readonly SqlSugarClient _db;

    public MultiTableBestPractices(SqlSugarClient db)
    {
        _db = db;
    }

    // 1. 合理使用导航属性
    public void UseNavigationWisely()
    {
        // 简单查询使用Join
        var simpleResult = _db.Queryable<User, Department>((u, d) => u.DepartmentId == d.Id)
            .Select((u, d) => new { u.Name, DeptName = d.Name })
            .ToList();

        // 需要完整对象使用导航属性
        var fullObjects = _db.Queryable<User>()
            .Includes(u => u.Department)
            .ToList();
    }

    // 2. 控制导航深度
    public List<Department> ControlNavigationDepth()
    {
        // 避免过深的导航层级
        return _db.Queryable<Department>()
            .Includes(d => d.Users)  // 只加载一层
            .ToList();
    }

    // 3. 使用DTO减少数据传输
    public List<UserDeptDto> UseDto()
    {
        return _db.Queryable<User, Department>((u, d) => u.DepartmentId == d.Id)
            .Select((u, d) => new UserDeptDto
            {
                UserId = u.Id,
                UserName = u.Name,
                DepartmentName = d.Name
            })
            .ToList();
    }

    // 4. 合理使用缓存
    public List<Department> UseCaching()
    {
        var cacheKey = "departments_with_users";
        
        // 检查缓存
        var cached = GetFromCache<List<Department>>(cacheKey);
        if (cached != null) return cached;

        // 查询数据库
        var data = _db.Queryable<Department>()
            .Mapper(d => d.Users, d => d.Users.First().DepartmentId)
            .ToList();

        // 写入缓存
        SetCache(cacheKey, data, TimeSpan.FromMinutes(10));

        return data;
    }

    // 5. 异步查询提高性能
    public async Task<List<Department>> UseAsyncQuery()
    {
        return await _db.Queryable<Department>()
            .Mapper(d => d.Users, d => d.Users.First().DepartmentId)
            .ToListAsync();
    }

    private T GetFromCache<T>(string key) where T : class
    {
        // 实现缓存获取逻辑
        return null;
    }

    private void SetCache<T>(string key, T value, TimeSpan expiration)
    {
        // 实现缓存设置逻辑
    }
}

本章小结

本章全面介绍了SqlSugar的多表联查与导航属性:

  1. 联表查询: 掌握了Inner Join、Left Join、Right Join和Full Join的使用
  2. 导航属性: 学习了如何定义和使用导航属性简化多表查询
  3. Mapper和Includes: 了解了两种加载关联数据的方法
  4. 一对一关系: 掌握了一对一关系的配置、查询和级联操作
  5. 一对多关系: 学习了一对多关系的处理和分页加载
  6. 多对多关系: 了解了多对多关系和中间表的操作
  7. 复杂多表查询: 掌握了三表、四表及以上的联表查询
  8. 性能优化: 学习了避免N+1查询、使用索引等优化技巧
  9. 最佳实践: 总结了多表查询的设计模式和注意事项

通过本章的学习,你应该能够熟练处理各种复杂的多表查询场景,并能够根据实际需求选择合适的查询方式。

下一章: 第08章-事务处理