链接查询
1. 在 DAO 层进行单表查询,Service 层调用两个 DAO 并进行多表查询后封装
优点:
- 代码复用性:单表查询的方法可以在不同的业务场景中复用。
- 职责分离:每个 DAO 负责单一的数据表操作,保持了职责的清晰和单一。
- 测试方便:单表查询的方法更容易进行单元测试。
- 灵活性:可以根据需要灵活组合不同的 DAO 方法来满足复杂的业务需求。
缺点:
- 多次数据库访问:可能会导致多次数据库访问,增加了网络延迟和资源消耗。
- 事务管理复杂:如果需要跨多个 DAO 的事务管理,可能会增加复杂性。
- 性能问题:多次查询可能会影响性能,特别是在大数据量的情况下。
2. 直接在 DAO 层进行多表查询并封装 VO(视图对象)
优点:
- 减少数据库访问:一次查询可以获取所有需要的数据,减少了网络延迟和资源消耗。
- 性能优化:通过减少数据库访问次数,可以显著提高性能。
- 事务管理简单:在一个事务中完成所有操作,简化了事务管理。
- 封装性好:可以直接返回一个封装好的 VO 对象,减少了服务层的处理逻辑。
缺点:
- 代码复用性较低:多表查询的方法通常更特定于某个业务场景,复用性较低。
- DAO 层复杂度增加:DAO 层的代码会变得更加复杂,因为需要处理多表关联和数据封装。
- 测试难度增加:多表查询的方法可能更难进行单元测试,尤其是当涉及到复杂的 SQL 查询时。
- 耦合性较高:DAO 层直接封装了多个表的数据,可能会导致更高的耦合性。
LINQ 查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| public DishPageResp GetPage(DishPageQueryReq dishPageQueryReq, bool isAsNoTracking = true) { Queryable<xiaobai_cangqiong_Storage.Entity.Dish> pagedDishes = _dbContext.Dishes;
if (isAsNoTracking) { pagedDishes = pagedDishes.AsNoTracking(); }
if (dishPageQueryReq.Name != null) { pagedDishes = pagedDishes.Where(d => d.Name.Contains(dishPageQueryReq.Name)); }
if (dishPageQueryReq.CategoryId != 0) { pagedDishes = pagedDishes.Where(d => d.CategoryId == dishPageQueryReq.CategoryId); }
if (dishPageQueryReq.Status != null) { pagedDishes = pagedDishes.Where(d => d.Status == dishPageQueryReq.Status); }
var total = pagedDishes.Count();
pagedDishes = pagedDishes.OrderBy(d => d.CreateTime) .Skip((dishPageQueryReq.Page - 1) * dishPageQueryReq.PageSize) .Take(dishPageQueryReq.PageSize); var dishPageVos = pagedDishes.Join(_dbContext.Categories, dish => dish.CategoryId, category => category.Id, (dish, category) => new DishPageVo { Id = dish.Id, Name = dish.Name, CategoryId = dish.CategoryId, Price = dish.Price, Image = dish.Image, Description = dish.Description, Status = dish.Status, UpdateTime = dish.UpdateTime, CategoryName = category.Name }); return new DishPageResp { Total = total, Records = dishPageVos.ToList(), }; }
|
内连接查询
在这里,我们链接查询后,手动将结果集映射到VO对象中
在这种对一关系中,我们可以直接简单的进行内连接查询,因为内连接查询效率最高
当有一个菜品的分类为空时,则需要用到外连接查询,但在咱们的项目中没有分类为空的菜品,所以没有这种需求
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| var dishPageVos = pagedDishes.Join(_dbContext.Categories, dish => dish.CategoryId, category => category.Id, (dish, category) => new DishPageVo { Id = dish.Id, Name = dish.Name, CategoryId = dish.CategoryId, Price = dish.Price, Image = dish.Image, Description = dish.Description, Status = dish.Status, UpdateTime = dish.UpdateTime, CategoryName = category.Name });
|
外连接查询
在这里,我们实际项目中的菜品数据是必须存在于分类的,那么使用链接查询即可完成业务需求
但如果存在菜品是没有被分类的,则需要用到外连接查询
GroupJoin
- 将主表(如
pagedDishes)中的每个元素与从表(如 _dbContext.Categories)中的元素进行分组连接。
- 结果是一个匿名对象,其中包含主表中的每个元素和一个从表中匹配的元素集合(如果有的话)。
SelectMany
- 使用
SelectMany 对 GroupJoin 的结果进行展平。
- 如果某个主表元素在从表中没有匹配项,则使用
DefaultIfEmpty() 方法提供一个默认值(通常为 null)。
- 最终结果是一个扁平化的集合,包含了所有主表元素及其对应的从表元素(如果有)或默认值(如果没有匹配项)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| var dishPageVos = pagedDishes .GroupJoin( _dbContext.Categories, dish => dish.CategoryId, category => category.Id, (dish, categoriesGroup) => new { Dish = dish, CategoriesGroup = categoriesGroup.DefaultIfEmpty() } ) .SelectMany( x => x.CategoriesGroup, (x, c) => new DishVo { Id = x.Dish.Id, Name = x.Dish.Name, CategoryId = x.Dish.CategoryId, Price = x.Dish.Price, Image = x.Dish.Image, Description = x.Dish.Description, Status = x.Dish.Status, UpdateTime = x.Dish.UpdateTime, CategoryName = c != null ? c.Name : "未分类" } ).ToList();
|
代码详解:
在进行.GroupJoin分组查询之后,产生的结果集(x)为一个元素(Dish)和其关联的结果集(CategoriesGroup)的集合
.SelectMany的第一个参数为x => x.CategoriesGroup,其含义是将其关联的结果集(CategoriesGroup)展平
这里的两个参数(x, category),x为结果集,category为 关联的结果集(CategoriesGroup)的元素中的一个 Category 对象,或者 null(如果没有匹配的 Category)
一对多关系处理
在Mybatis中,我们使用resultMap和collection来完成一对多的关系处理
在使用GroupJoin方法进行外连接查询后,第一个类型中的每一个元素都包含一个第二个类型的一个元素集合
我们使用SelectMany的方式展开,就实现了数据库左外连接查询的结果
所以通过一个简单的GroupJoin配合Select,就在ASP.NETCore中完成了一对多关系的处理
在这里,我们不需要将dishFlavor结果集展平,因为我们需要的是Flavor(List<DishFlavor>)集合,所以直接ToList为其赋值即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| IQueryable<xiaobai_cangqiong_Storage.Entity.Dish> queryable = _dbContext.Dishes; queryable.GroupJoin(_dbContext.DishFlavors , d => d.Id , d => d.DishId , (dish, dishFlavor) => new { Dish = dish, DishFlavor = dishFlavor.DefaultIfEmpty() }) .Select(x => new DishListResp { Id = x.Dish.Id, Name = x.Dish.Name, CategoryId = x.Dish.CategoryId, Price = x.Dish.Price, Image = x.Dish.Image, Description = x.Dish.Description, Status = x.Dish.Status, UpdateTime = x.Dish.UpdateTime, Flavors = x.DishFlavor.ToList(), });
|
LINQ表达式语法
我们发现,在进行多表的连接查询时,LINQ的方法语法会比表达式语法更加麻烦
再表达式语法中,如果进行外连接查询,会自动处理Flavors = flavors.DefaultIfEmpty(),且这种写法简单,更符合SQL语义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| var dishPageVos = from dish in pagedDishes join category in _dbContext.Categories on dish.CategoryId equals category.Id select new DishPageVo { Id = dish.Id, Name = dish.Name, CategoryId = dish.CategoryId, Price = dish.Price, Image = dish.Image, Description = dish.Description, Status = dish.Status, UpdateTime = dish.UpdateTime, CategoryName = category.Name };
var dishListResps = ( from dish in _dbContext.Dishes join dishFlavor in _dbContext.DishFlavors on dish.Id equals dishFlavor.DishId into dishFlavorGroup select new DishListResp { Id = dish.Id, Name = dish.Name, CategoryId = dish.CategoryId, Price = dish.Price, Image = dish.Image, Description = dish.Description, Status = dish.Status, UpdateTime = dish.UpdateTime, Flavors = dishFlavorGroup.ToList() } ).ToList();
var dishPageVos = ( from dish in pagedDishes join category in _dbContext.Categories on dish.CategoryId equals category.Id into categoriesGroup from c in categoriesGroup.DefaultIfEmpty() select new DishVo { Id = dish.Id, Name = dish.Name, CategoryId = dish.CategoryId, Price = dish.Price, Image = dish.Image, Description = dish.Description, Status = dish.Status, UpdateTime = dish.UpdateTime, CategoryName = c != null ? c.Name : "未分类" } ).ToList();
|
总结
对于数据库关系来说,对一关系使用内连接来完成,高效简单,对于有需求用到外连接的内容,使用from子句展平对象后取值即可
对多关系多用外连接处理,使用into关键字将被链接表做成集合,可调用ToList转至集合后直接赋值到VO对象的属性中
对于LINQ的语法方式选择来说,简单语句使用LINQ方法语法更简单,利用Lambda表达式合适
复杂语法,涉及到多表查询的语法要选择方法语法简单,连接写起来更类SQL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| var dishListResps = from dish in queryable join dishFlavor in _dbContext.DishFlavors on dish.Id equals dishFlavor.DishId into dishFlavorGroup join category in _dbContext.Categories on dish.CategoryId equals category.Id select new DishListResp { Id = dish.Id, Name = dish.Name, CategoryId = dish.CategoryId, Price = dish.Price, Image = dish.Image, Description = dish.Description, Status = dish.Status, UpdateTime = dish.UpdateTime, CategoryName = category.Name, Flavors = dishFlavorGroup.ToList() };
|