首页 > 其他分享 >EF Core从TPH迁移到TPT

EF Core从TPH迁移到TPT

时间:2023-04-04 11:56:19浏览次数:40  
标签:Core 子类 数据库 EF TPH TPT

EF Core从TPH迁移到TPT

 

Intro#

EF Core支持多种方式处理具有继承关系的表,现在支持TPHTPC(EF Core 7)、TPT,具体的实现方式可以参考官方文档这篇文章

大致总结一下不同的方式的区别:
TPH:所有的类型都放在一张表中,使用discriminator字段用以区别不同的类型
TPT:不同的子类型有单独的表存放子类独有的字段,父虚类型也有一张单独的表存放共有的字段。
TPC:不为父虚类新建表,只有子类型有单独的表,并且表内有父类和子类所有的字段。

由于TPT两张表的外键关联设计,在进行查询时,会自动进行的JOIN等连表查询操作,因此极限性能不太行。需要经常用查询父类的情况,TPH就挺好;需要经常查询子类的时候,TPC就非常适合。按照官方的说法,正常情况TPH就已经满足大多数的场景(这也是EF Core的默认设置),性能也是数一数二的,如果遇到了需要经常单独查询子类型的问题,可以优先考虑TPC,仅在一些特殊情况下应该考虑TPT。哪些是特殊情况?

请查阅官网这篇文章的详细讨论以了解三种不同方式对EF Core生成SQL的影响。

可能适合的场景#

我遇到的这么一个场景,有以下特点:

  • 子类非常多,并且不同的子类字段的区别也很大,使用TPH会使得这个表格的规格非常大,并且空字段非常多。
  • 继承的层级很短,只有一层继承关系。
  • 需要经常进行基于父类的查询,直接在一张表执行查询的效率要比在的TPC分布在不同表中查询的效率高。(注意,这里说的父类的查询是指直接使用Raw SQL的查询,使用EF Core在父类的查询会翻译成非常多的LEFT JOIN,导致性能低下。)

直接使用TPH或者使用TPC都不是非常满意,而TPT提供了一张父类的表存储公共的字段的这种方法,就显得非常适合。

注:TPC不符合数据库范式设计原则,TPH在空字段非常多的情况下也非常不优雅,强迫症可以使用TPT。

迁移#

如果是空表的话,直接使用EF Migration就可以了,麻烦的已经有既有数据的情况,由于数据表引用的对象从的总表转移到了子类表,因此直接执行的数据库迁移会提示违反了外键约束。

23503: insert or update on table "AD_AnimalCamera_Data" violates foreign key constraint "FK_AD_AnimalCamera_Data_AD_AnimalCamera_Infos_AttachDeviceId"

解决方案:

  1. 手动创建表,并将TPH表中的不同的子类型记录转移到不同的子类表中。
  2. 通过自编程序载入对象,进行持久化,然后清空所有表的数据,创建表,载入数据并通过EF Core插入。

由于数据量比较大,而且还有继承关系,手动去操作还是麻烦了一些,可以使用SQL查询进行简化;而第二个方案将由EF Core帮我们将数据插入到正确的位置。

方案1#

准备临时数据库#

将原来的数据库结构复制一份,并设置为开发环境。接下来修改数据库结构,TPH迁移到TPT模式,只需要在每一个子类表上使用[Table("")]标记就行了(当然也可以使用FluentAPI)。标记好了之后,使用EF Migration:

add-migration migrateTPT

由于是只有结构的空表,直接操作就可以成功了。

迁移数据到临时数据库#

将旧有数据传输到新的数据表中,尤其注意TPH与TPT之间表的在处理继承关系时的不同。

以AttachDeviceInfo为abstract类,AD_Insect_Info作为其中的一个子类

更新之后TPH表中的大量字段转移到了子类表中,因此可以使用数据库同步工具进行数据同步,忽略多余的字段就可以了。对于的TPT生成的子类表,通过Id字段与抽象类表进行匹配连接,因此需要手动插入对应类别的数据。

INSERT into "AD_Insect_Infos"
SELECT "Id",FALSE from "AttachDeviceInfos" WHERE "AttachDeviceTypeId" = 1

如果没有AttachDeviceTypeId字段,那么需要在TPH阶段先通过discriminator将不同子类区分开,这个会麻烦一点。

转移回数据库#

清空目标数据库(包括结构),并将临时数据库中的表同步到目标数据库中,手动调整_EFMigration表格的记录(指向最新版本),完成切换。

方案2#

备份数据#

在数据库还是原来结构的情况下,我们需要将现有的数据进行序列化,之前我写过一篇序列化文章,使用的是PROTOBUF序列化。这里由于传输的数据结构比较简单,可以使用System.Text.Json类库Json序列化到文件。

对于有继承关系的表的序列化,.NET 7的System.Text.Json新增了对应的支持,可以参考文档的相关实现。

准备临时数据库#

将原来的数据库结构复制一份,并设置为开发环境。接下来修改数据库结构,TPH迁移到TPT模式,只需要在每一个子类表上使用[Table("")]标记就行了(当然也可以使用FluentAPI)。标记好了之后,使用EF Migration:

add-migration migrateTPT

由于是只有结构的空表,直接操作就可以成功了。

迁移数据到临时数据库#

由于临时数据库结构已经和既有数据库不同,无法通过程序直接连接两个数据库进行数据导入的操作,因此需要将数据反序列化到的新的数据库。

转移回数据库#

清空目标数据库(包括结构),并将临时数据库中的表同步到目标数据库中,手动调整_EFMigration表格的记录(指向最新版本),完成切换。

总结#

迁移到TPT时,可以使用临时数据库中转,将数据库的数据以新的结构存储下来,然后再同步到新数据库。当然也可以直接在正式数据库中操作:直接持久化,清空数据,然后再还原数据。当然这么风险更高,强调一点,在生产的数据库中进行操作需要格外谨慎,务必做好备份。

可以发现,在数据库中使用外键约束时,虽然给基于导航属性的应用(例如OData)提供了便利,同时将数据完整性检查后置到了数据库中;但是进行架构调整是一件比较麻烦的工作,对分布式应用也非常不友好。

P.S. TPT的查询性能很差,因此绝大多数场景都不推荐,仅在自己完全清楚并权衡了利弊的情况下再使用TPT。

作者:波多尔斯基

出处:https://www.cnblogs.com/podolski/p/17284449.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

欢迎转载,转载请保留原文链接~喜欢的观众老爷们可以点下推荐或者右下角关注不迷路~

标签:Core,子类,数据库,EF,TPH,TPT
From: https://www.cnblogs.com/sexintercourse/p/17285933.html

相关文章

  • git pull 报错提示 fatal: refusing to merge unrelated histories
    从远程拉到本地的时候提示错误 造成原因:1.远程仓库和本地仓库内容不相关,合并不兼容。2.目录有问题,.git可能意外被删除。如果克隆或清理项目时可能会发生这种情况。3.从远程仓库拉取或推送数据时,分支位于不同的HEAD位置,并且由于缺乏共性而不发匹配。 我出......
  • EF Core从TPH迁移到TPT
    IntroEFCore支持多种方式处理具有继承关系的表,现在支持TPH、TPC(EFCore7)、TPT,具体的实现方式可以参考官方文档和这篇文章。大致总结一下不同的方式的区别:TPH:所有的类型都放在一张表中,使用discriminator字段用以区别不同的类型TPT:不同的子类型有单独的表存放子类独有的字段,父......
  • Error response from daemon: conflict: unable to remove repository reference "mic
    docker删除镜像时出现了这样的问题解决方法:强制删除docerrmi-f[IMAGEID]......
  • undefined symbol问题的查找、定位与解决方法
    今天被客户测出来一个问题:程序执行中报错,报错内容如下XXXX:symbollookuperror:/home/....../libpdfium.so:undefinedsymbol:CRYPT_MD5Generate报错分析:    这个问题表明是符号未定义的问题,而且直接定位于产品链接的第三方动态库libpdfium.so中,于是从libpdfium.so中......
  • 关于 Angular 应用部署时的 base-href 参数
    import{existsSync}from'fs';server.get(['/shop/*','/shop'],(req,res)=>{res.render(indexHtml,{req,providers:[{provide:APP_BASE_HREF,useValue:req.baseUrl}],});});server.set('views',distFo......
  • Angular 应用里 server.ts 文件的 APP_BASE_HREF token 的用法?
    Angular应用里server.ts文件,下面这段代码的含义?server.get('*',(req,res)=>{res.render(indexHtml,{req,providers:[{provide:APP_BASE_HREF,useValue:req.baseUrl}],});});在Angular应用中,server.ts文件是用于构建服务器端渲染(S......
  • Select all in address bar of firefox (Ubuntu, Linux Mint) with a single click
    Toomanythingstomodifyafterinstallingubuntuorlinuxmint.OneoftheannoyingthingisClickingtheaddressbaronFirefox(providedwithUbuntu,linuxmint)doesn’tselectthewholetextinit.Itsaverysmallproblem.Butitssoannoyingthat,t......
  • 技嘉 B360 HD3 Core i7-8700 GTX1060黑苹果efi引导文件
    原文来源于黑果魏叔官网,转载需注明出处。(下载请直接百度黑果魏叔)硬件型号驱动情况主板技嘉B360HD3(B360芯片组)处理器英特尔[email protected]六核已驱动内存32GB(现代DDR42666MHz16GB/金邦DDR43000MHz16GB)已驱动硬盘技嘉GP-GSTFS31120GNTD(120GB/固态硬......
  • Codeforces Round 862 (Div. 2) A-D题解
    比赛地址A.WeNeedtheZero题意:给出一个数组,对任意1<=i<=n,令bi=aix,问是否存在x,使得b<sub>1</sub>b2...bn=0Solution如果n为奇数,那么x一定存在,因为偶数个x异或得到的是0,直接令x=0(a<sub>1</sub>a2...an)即可如果n为偶数,那么x取任何值都不会影响结果,所以只用看a1a<sub>2</sub......
  • Codeforces Round 862 (Div. 2) (4.2)
    CodeforcesRound862(Div.2)A-WeNeedtheZero思路:某个数被异或两次相当于没变,即判断n的奇偶性;n为偶数时判断所有数异或后的数是否为0,若为0,输出任意数;n为奇数时答案为所有数异或后的值#include<bits/stdc++.h>usingnamespacestd;typedefpair<int,int>PII;consti......