最近做个分库分表项目,用到schema的切换感觉还是有些坑的,在此分享下。
先简要说下我们的分库分表
分库分表规则
我定的规则是,订单号(数字)除以16,得出的结果为这个订单所在的数据库,然后他的余数代表他所在这个库里面的哪个表。
然后在一个库里面有16个表,这个怎么实现呢?比较龊的办法是 Order1/Order2这样,不过后来我想了下,数据库默认(我们是Sql Server)是有schema的(默认是dbo的那个东西),
然后我就打起这个东西的主意,后续跟dba确认方案可行后,就决定比如在同一个库里,第一套订单表是 p0.Order,第二套订单表就是p1.Order(有效取值 p0~p15)。
代码设计
如果说每次操作订单,你都要记得要先根据订单号拆分规则,找到这是哪个数据库,再去找这是哪个schema,我觉得这代码也别写了,放弃把,这是不可维护的代码,不具备可持续发展性。
我们必须要将一个分库分表这么一件事,抽象为某个单一的逻辑。
于是乎,DbContext是个好东西!
当我们在说Ef的时候,实际上我们在讨论的主要就是他里面的DbContext。而一个DbContext,则逻辑上代表了一个数据库映射(包含数据库连接/表等和数据库相关的所有配置的集合)。
只要我们将分库分表这件事,抽象为如何获取一个DbContext,之后在对这个DbContext做你想做的操作,那么一切事情就简单多了!
于是乎我设计了这么一个接口:
你给我一个订单号,我还你一个DbContext~
正题
如何让DbContext支持分库分表
这里主要有2个问题,一个是如何改连接字符串,另一个如何改schema。
问题1简单,创建DbContext的时候本来就是要串连接字符串进去的,直接这里构造好连接字符串即可。
问题2比较复杂,也是这篇文章主要内容,首先我的设计是在创建DbContext,传入schema,在OnModelCreating里用这个schema初始化。
代码是这样子的:
于是乎你第一次创建DbContext的时候,schema是什么(别在意我代码变量名当时写错了这么个细节),就永远是什么,后续你重复创建其他DbContext的时候其实这句话并不能让你修改schema。
后面我发觉,EfCore在指定表名的时候,是可以顺带指定schema的,于是乎在改下,改成了类似这样:
然而实践证明依然并没有什么卵用。
坑了2次后我严重怀疑efcore里一定有某种级别的缓存机制,使得初次赋值之后某些信息不会再被更新(甚至于怀疑到efcore2.x引入的DbContextPool,然而我都是new出来的我没pool啊)。
后面一通乱找后不记得再哪个网址(但是记得一定是stackoverflow里)找到了对这个类 IModelCacheKeyFactory的一些描述。
好像是efcore会对Model(你的实体)和DbContext之间产生一个缓存,而我的分库分表用的DbContext只有一个(只是动态修改了某些参数配置),于是乎觉得应该就是这个东西缓存了的关系导致,然后我重写了这货的实现:
本质就是将DbContext里的当前的Schema暴露出给ModelCacheKey读取,然后进行Equal比较的时候Schema也作为一个Equal的因素,当两者比较不等的时候,就不会再采用之前错误Schema的缓存了
最后在创建DbContextOptionBuilder的时候Replace一下
builder.ReplaceService<IModelCacheKeyFactory, SchemaModelCacheFactory>();
即可完成
后续亲测都能动态切换schema,圆满完成。
后面发现(评论里提到的)其实这里已经有说明这个问题了,文档看不仔细,躺坑两行泪
https://docs.microsoft.com/zh-cn/ef/core/modeling/dynamic-model