EFCore_一些注意事项
主键策略
自增主键
1、EF Core支持多种主键生成策略:自动增长、Guid、Hi/Lo算法等。
2、自动增长优点:简单;缺点:数据库迁移以及分布式系统中比较麻烦;并发性能差。long、int等类型主键,默认是自增的。因为是数据库生成的值,所以SavaChanges后会自动把主键的值更新到Id属性。
3、自增字段的代码中不能为Id赋值,必须保持默认值0,否则运行的时候会报错。
Guid主键
1、Guid算法(或UUID算法)生成一个全局唯一的Id。适合于分布式系统,在进行多数据库数据合并时很简单。优点:简单、高并发效率比自增主键高、全局唯一;缺点:磁盘空间占用大。
2、Guid值不连续。使用Guid类型做主键的时候,不能把主键设置为聚集索引。因为聚集索引是按照顺序保存主键的,因此用Guid做主键性能差。比如MySql的InnoDB引擎中主键是强制使用聚集索引的。有的数据库支持部分的连续Guid,比如SQLServer中的NewSequentialId(),但也不能解决问题。在SQLServer等中,不要把Guid主键设置为聚集索引;在MySQL中,插入频繁的表不要用Guid做主键。
1 | //Guid是结构体,一般不使用new Guid(),当然这样也没问题,但是更推荐以下写法 |
其他方案
混合自增与Guid(非复合主键):
用自增列做物理的主键,而用Guid列做逻辑上的主键。把自增列设置为表的主键,而在业务上查询数据的时候把Guid当主键使用。在和其他表关联以及和外部系统通讯的时候(比如前端显示数据的标识的时候)都是使用Guid列。不仅保证了性能,而且利用了Guid的优点,减轻了主键自增性导致主键值可被预测带来的安全性问题。
Hi/Lo算法:
EF Core 支持Hi/Lo算法来优化自增列。主键值由两部分组成:高位(Hi)和低位(Lo),高位由数据库生成,两个高位之间间隔若干个值,由程序在本地生成低位,低位的值在本地自增生成。不同进程或者集群中不同服务器获取的Hi值不会重复,而本地进程计算的Lo则可以保证可以在本地高效率的生成主键值。但是HiLo算法不是EF Core的标准。
Migrations
Migrations原理
1、使用迁移脚本,可以对当前连接的数据库执行编号更高的迁移,这个操作叫做“向上迁移”(Up),也可以执行把数据库回退到旧的迁移,这个操作叫“向下迁移”(Down)。
2、除非有特殊需要,否则不要删除Migrations文件夹下的代码。
3、在Migrations文件夹下的文件记录着每个操作事件的Up和Down操作。
4、数据库的__EFMigrationsHistory表:记录当前数据库曾经应用过的迁移脚本,按顺序排列。
Migrations其他命令
1、Updata-Database XXX
把数据库升级/回滚到XXX的状态,迁移脚本不动。
2、Remove-Migration
删除最后一次的迁移脚本。
3、Script-Migration
生成迁移SQL代码。因为Updata-Database对数据可的操作没有直接的Sql代码清晰,可以通过该语句直观看到对数据库的操作。
4、Script-Migration D F
生成版本D到版本F的SQL脚本。
5、Script-Migration D
生成版本D到最新版本的SQL脚本。
反向工程
通过数据库表来反向生成实体类:
程序包管理控制台输入
Scaffold-DbContext “Server=.;Database=demo1;Trusted_Connection=True;MultipleActiveResultSets=true” Microsoft.EntityFrameworkCore.SqlServer
如果已经生成了一个实体类,但是想再在数据库中再增加一个实体类需要加一个参数 -Force:
Scaffold-DbContext “Server=.;Database=demo1;Trusted_Connection=True;MultipleActiveResultSets=true” Microsoft.EntityFrameworkCore.SqlServer -Force
通过代码查看EF Core生成的Sql语句
1、标准日志
1 | namespace Interview { |
2、简单日志
1 | namespace Interview { |
3、ToQueryString方法
上面两种方式无法直接得到一个操作的SQL语句,而且在操作很多的情况下容易混乱。
EF Core的Where方法返回的而是IQueryable类型,DbSet也实现了IQueryable接口。IQueryable有拓展方法ToQueryString()可以获得转换后的SQL语句。
不需要真的执行查询才获取SQL语句;只能获取查询操作的SQL语句。
1 | namespace Interview { |
4、总结
写测试性代码,用简单日志;正式需要记录SQL给审核人员或者排查故障,用标准日志;开发阶段,从繁杂的查询操作中立即看到SQL,用ToQueryString()。
同样的Linq被翻译成不同的SQL语句
数据库迁移脚本不能跨数据库,因为各个数据库的建表语句或者创建字段语句不一样。可以通过给Add-Migration命令添加“-OutputDir”参数的形式来在同一项目中为不同的数据库生成不同的迁移脚本。
由于各个数据库的查询、函数、自增等语法不同,在不同的EF Provider中相同的语法有可能产生不同的SQL语句。