EFCore多数据库合并查询分页
参照:二个表的数据 如何做分页?_两个表排序分页_深圳市热心市民市民的博客-CSDN博客
基本情况介绍:由于系统迭代,部分收藏表在老系统的数据库,部分在新api接口的数据库,现在有一个需求是在个人中心展示用户收藏的数据,按照收藏时间倒序排列,因为在APP端实际上就算瀑布流分页。
主体思路:通过将两个表通过条件筛选形成IQuerable,再通过Union联结,做的分页,再通过ToList()加载到内存里
query1.Union(query2).Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList();
但是经过尝试后,报错:Cannot use multiple DbContext instances within a single query execution。在一个查询里只能使用一个数据库上下文实例。因为来自两个不同的数据库所以query1和query2我们使用了两个不同的 DBContext
,配置了不同的数据库连接。结果就出现了上述的报错。转念又想有没有可能在一个负责管理实体对象的上下文中设置多个数据库那不就可以了吗?但是查阅资料,发现并不能实现。因为当存在多个数据库在 DbContext
里的 DbSet
没办法指定它属于哪个数据库。简而言之,就是一个 DBContext
只能对应一个数据库。
所以思路调整为从两个 DBContext
里查找出结果集(ToList()
加载到内存里),再将结果集合并后再做分页,这就涉及到怎么取数据能得到准确的结果还能使得查询效率最大化?假设我们是按照创建时间逆序,每页10条:
这里有一个容易犯错的思路:假设是第一页,先从A表里取10里取5条,再从B表里取5条,合并后再按照时间排序输出。这样做最大的问题是,没办法保证数据的准确性。因为极端一点可能最新的10条数据都来自B表,这样取是没办法保证数据准确性的。
以上错误的做法也给了我们参考意义,那各取多少条就能保证数据是准确的呢?答案是 pageNumber*pageSize
为什么呢?以第一页为例,假设每页10条,我从A表里取10条,再从B表里取10条,从合并后的20条里找出最新的10条,这是肯定合理的,它能涵盖这页数据全部来自其中一张表的情况。以此类推,第二页:各取20条;第三页:各取30条...以此类推。很容易发现问题,就是约到后面的页码,我们要取得数据越多。基本上是线性增加的,那就意味着到了后面的页码会出现我们之前不愿见到的局面:我们把大量的结果集都加载到了内存里,再进行合并排序分页。
参照的文章给了我们思路。因为我们分页基本上是在排序后进行的,所以我们每次分页的最后一条数据,可以作为下一页数据的的筛选判断条件。以我们文中例子,第一页第10条数据的创建时间一定是大于第二页的数据的。我们就可以在调用接口时除了我们的页码参数,把前一页码数据最后一条的创建时间也带上当作参数传入。结果是
var result1 = dbContext1.Colections.where(s=>s.CreateTime< timeParm).OrderByDescending(s=>s.CreateTime).Take(pageSize).ToList();
var result2 =dbContext2.Colections.where(s=>s.CreateTime< timeParm).OrderByDescending(s=>s.CreateTime).Take(pageSize).ToList();
仅仅只需要每次各从结果集取出pageSize条,甚至不需要pageNum的参数就能实现我们需要的效果。另外它还有个隐藏性能福利:我们通过where的筛选实现了 Skip((pageNumber - 1) * pageSize)
的效果,性能会更高。每次取出加载到内存的结果集都很小,实现了我们预期的效果。