znlgis 博客

GIS开发与技术分享

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

目录

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. 最佳实践: 总结了多表查询的设计模式和注意事项

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

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