type
status
date
slug
summary
tags
category
icon
password
- 1、Entity Framework Core(EF Core)是微软官方的ORM框架。优点:功能强大、官方支持、生产效率高、力求屏蔽底层数据库差异;缺点:复杂、上手门槛高、不熟悉EFCore的话可能会进坑。
- 2、Dapper。优点:简单,N分钟即可上手,行为可预期性强;缺点:生产效率低,需要处理底层数据库差异。
- 3、EF Core是模型驱动(Model-Driven)的开发思想,Dapper是数据库驱动(DataBase-Driven)的开发思想的。没有优劣,只有比较。
- 4、性能: Dapper等≠性能高;EF Core≠性能差。
- 5、EF Core是官方推荐、推进的框架,尽量屏蔽底层数据库差异,.NET开发者必须熟悉,根据的项目情况再决定用哪个。
对于后台系统、信息系统等和数据库相关开发工作量大的系统,且团队比较稳定,用EF Core;对于互联网系统等数据库相关工作量不大的系统,或者团队不稳定,用Dapper。
环境搭建
Migration数据库迁移
面向对象的ORM开发中,数据库不是程序员手动创建的,而是由Migration工具生成的。关系数据库只是盛放模型数据的一个媒介而已,理想状态下,程序员不用关心数据库的操作。
根据对象的定义变化,自动更新数据库中的表以及表结构的操作,叫做Migration(迁移)。
迁移可以分为多步(项目进化),也可以回滚。
- 为了使用生成数据库的工具,Nuget安装Microsoft.EntityFrameworkCore.Tools,否则执行Add-Migration等命令会如下报错
- 1、再在“程序包管理器控制台”中执行如下命令 Add-Migration InitialCreate 会自动在项目的Migrations文件夹中中生成操作数据库的C#代码。讲解一下生成代码的作用。 InitialCreate是什么? 是对项目的声明
- 2、代码需要执行后才会应用对数据库的操作。“程序包管理器控制台”中执行Update-database。
- 3、查看一下数据库,表建好了
深入研究Migrations
- 1、使用迁移脚本,可以对当前连接的数据库执行编号更高的迁移,这个操作叫做“向上迁移”(Up),也可以执行把数据库回退到旧的迁移,这个操作叫“向下迁移”(Down)。
- 2、除非有特殊需要,否则不要删除Migrations文件夹下的代码。
- 3、进一步分析Migrations下的代码。分析Up、Down等方法。查看Migration编号。
- 4、查看数据库的__EFMigrationsHistory表:记录当前数据库曾经应用过的迁移脚本,按顺序排列。
Migrations其他命令
- 1、Update-Database XXX 把数据库回滚到XXX的状态,迁移脚本不动。
- 2、Remove-migration 删除最后一次的迁移脚本
- 3、Script-Migration 生成迁移SQL代码。有了Update-Database 为什么还要生成SQL脚本。
3、Update-database只适合在开发环境下使用。很多公司是要求对生产环境数据库的操作要审计。如果生产数据库已经是处于某个迁移版本的状态了,那么可以生成这个版本到某个新版本的SQL脚本。4、开发环境用Update-database,生产环境根据公司需要选择是Update-database还是生成SQL脚本。
- 可以生成版本D到版本F的SQL脚本: Script-Migration D F 生成版本D到最新版本的SQL脚本:Script-Migration D
- 因此迁移脚本不能跨数据库。通过给Add-Migration命令添加“-OutputDir”参数的形式来在同一个项目中为不同的数据库生成不同的迁移脚本。本节简化为建不同项目
修改表结构
- 1、项目开发中,根据需要,可能会在已有实体中修改、新增、删除表、列等。
- 2、想要限制Title的最大长度为50,Title字段设置为“不可为空”,并且想增加一个不可为空且最大长度为20的AuthorName(作者名字)属性。 首先在Book实体类中增加一个AuthorName属性
- 3、修改BookEntityConfig
- 4、执行Add-Migration AddAuthorName_ModifyTitle。取名字有意义
- 5、Update-Database
增删改查
插入数据
- 1、只要操作Books属性,就可以向数据库中增加数据,但是通过C#代码修改Books中的数据只是修改了内存中的数据。对Books做修改后,需要调用DbContext的异步方法SaveChangesAsync()把修改保存到数据库。也有同步的保存方法SaveChanges(),但是用EF Core都推荐用异步方法。
- 2、EF Core默认会跟踪(Track)实体类对象以及DbSet的改变。
查询数据
- 1、DbSet实现了IEnumerable<T>接口,因此可以对DbSet实施Linq操作来进行数据查询。EF Core会把Linq操作转换为SQL语句。面向对象,而不是面向数据库(SQL)。
- 2、
- 3、可以使用OrderBy操作进行数据的排序
插入
1、要对数据进行修改,首先需要把要修改的数据查询出来,然后再对查询出来的对象进行修改,然后再执行SaveChangesAsync()保存修改。
删除
批量修改、删除
- 1、目前批量修改、删除多条数据的方法。 局限性:性能低:查出来,再一条条Update、Delete,而不能执行Update ... where;delete ... where;
- 2、官方目前还没有支持高效的批量Update、Delete,有在后续版本中增加,但是目前只是前期意见征询阶段。
- 3、杨中科实现了一个开源的高效批量修改、删除的开源项目。
约定配置
主要规则:
- 1:表名采用DbContext中的对应的DbSet的属性名。
- 2:数据表列的名字采用实体类属性的名字,列的数据类型采用和实体类属性类型最兼容的类型。
- 3:数据表列的可空性取决于对应实体类属性的可空性。
- 4:名字为Id的属性为主键,如果主键为short, int 或者 long类型,则默认采用自增字段,如果主键为Guid类型,则默认采用默认的Guid生成机制生成主键值。
两种配置方式
- 1、Data Annotation 把配置以特性(Annotation)的形式标注在实体类中。 [Table("T_Books")] public class Book { } 优点:简单;缺点:耦合。
- 2、Fluent API builder.ToTable("T_Books"); 把配置写到单独的配置类中。 缺点:复杂;优点:解耦
- 3、大部分功能重叠。可以混用,但是不建议混用。
Fluent API
- 1、视图与实体类映射:
- 2、排除属性映射:
- 3、配置列名:
- 4、配置列数据类型:
- 5、配置主键 默认把名字为Id或者“实体类型+Id“的属性作为主键,可以用HasKey()来配置其他属性作为主键。
支持复合主键,但是不建议使用。
- 6、生成列的值
- 7、可以用HasDefaultValue()为属性设定默认值
- 8、索引
复合索引
唯一索引:IsUnique();聚集索引:IsClustered()
Fluent API中很多方法都有多个重载方法。比如HasIndex、Property()。 把Number属性定义为索引,下面两种方法都可以: builder.HasIndex("Number"); builder.HasIndex(b=>b.Number);推荐使用HasIndex(b=>b.Number)、Property(b => b.Number)这样的写法,因为这样利用的是C#的强类型检查机制
- 9... 用EF Core太多高级特性的时候谨慎,尽量不要和业务逻辑混合在一起,以免“不能自拔”。比如Ignore、Shadow、Table Splitting等……
主键
- 1、 EF Core支持多种主键生成策略:自动增长;Guid;Hi/Lo算法等。
- 2、自动增长。
优点:简单;缺点:数据库迁移以及分布式系统中比较麻烦;并发性能差。
long、int等类型主键,默认是自增。因为是数据库生成的值,所以SaveChanges后会自动把主键的值更新到Id属性。试验一下。场景:插入帖子后,自动重定向帖子地址。
- 3、自增字段的代码中不能为Id赋值,必须保持默认值0,否则运行的时候就会报错。
Guid主键
- 1、 Guid算法(或UUID算法)生成一个全局唯一的Id。适合于分布式系统,在进行多数据库数据合并的时候很简单。优点:简单,高并发,全局唯一;缺点:磁盘空间占用大。
- 2、Guid值不连续。使用Guid类型做主键的时候,不能把主键设置为聚集索引。因为聚集索引是按照顺序保存主键的,因此用Guid做主键性能差。比如MySQL的InnoDB引擎中主键是强制使用聚集索引的。有的数据库支持部分的连续Guid,比如SQLServer中的NewSequentialId(),但也不能解决问题。在SQLServer等中,不要把Guid主键设置为聚集索引;在MySQL中,插入频繁的表不要用Guid做主键。
- 3、演示Guid用法:既可以让EF Core给赋值,也可以手动赋值(推荐)。
其他方案
- 1、 混合自增和Guid(非复合主键)。用自增列做物理的主键,而用Guid列做逻辑上的主键。把自增列设置为表的主键,而在业务上查询数据时候把Guid当主键用。在和其他表关联以及和外部系统通讯的时候(比如前端显示数据的标识的时候)都是使用Guid列。不仅保证了性能,而且利用了Guid的优点,而且减轻了主键自增性导致主键值可被预测带来的安全性问题。
- 2、Hi/Lo算法:EF Core支持Hi/Lo算法来优化自增列。主键值由两部分组成:高位(Hi)和低位(Lo),高位由数据库生成,两个高位之间间隔若干个值,由程序在本地生成低位,低位的值在本地自增生成。不同进程或者集群中不同服务器获取的Hi值不会重复,而本地进程计算的Lo则可以保证可以在本地高效率的生成主键值。但是HiLo算法不是EF Core的标准。
反向工程
- 1、根据数据库表来反向生成实体类
- 2、
1、生成的实体类可能不能满足项目的要求,可能需要手工修改或者增加配置。 2、再次运行反向工程工具,对文件所做的任何更改都将丢失。 3、不建议把反向工具当成了日常开发工具使用,不建议DBFirst。“模型驱动”与“数据库驱动”DBFirst:前期很爽,后期越来越痛苦。
EF Core把C#代码转换为SQL语句的框架。
通过代码查看EF Core
生成的SQL
方法1:标准日志
方法2:简单日志
方法3:ToQueryString
- 1、上面两种方式无法直接得到一个操作的SQL语句,而且在操作很多的情况下,容易混乱。
- 2、EF Core的Where方法返回的是IQueryable类型,DbSet也实现了IQueryable接口。 IQueryable有扩展方法ToQueryString()可以获得SQL。
- 3、不需要真的执行查询才获取SQL语句;只能获取查询操作的。
总结:写测试性代码,用简单日志;正式需要记录SQL给审核人员或者排查故障,用标准日志;开发阶段,从繁杂的查询操作中立即看到SQL,用ToQueryString()。
使用MySql
EF Core
一对多关系配置
- 1、所谓“关系数据库”
- 2、复习:数据库表之间的关系:一对一、一对多、多对多。
- 3、EF Core不仅支持单实体操作,更支持多实体的关系操作。
- 4、三部曲:实体类中关系属性;FluentAPI关系配置;使用关系操作。
一对多:实体类
- 1、文章实体类Article、评论实体类Comment。一篇文章对应多条评论。
一对多:关系配置
一对多:试验
- 1、迁移生成数据库表。
- 2、编写代码测试数据插入。
- 3、不需要显式为Comment对象的Article属性赋值(当前赋值也不会出错),也不需要显式地把新创建的Comment类型的对象添加到DbContext中。EF Core会“顺竿爬”。
优化:
一对多特殊类型自引用的组织结构树
EF Core
一对一关系配置
- 在“一对多”的关系中,很显然是需要在“多”端有一个指向“一”端的列,因此除非我们需要显式的声明一个外键属性,否则EF Core会自动在“多”端的表中生成一个指向“一”端的外键列,不需要我们显式的声明一个外键属性。
- 对于一对一关系,由于双方是“平等”的关系,外键列可以建在任意一方,因此必须显式的在其中一个实体类中声明一个外键属性。
一对一:实体类
必须显式的在其中一个实体类中声明一个外键属性。
一对一:关系配置
一对一:试验
EF Core
多对多关系配置
- 1、多对多:老师—学生。
- 2、EF Core 5.0开始,才正式支持多对多
- 3、需要中间表,举例数据。
多对多:实体类
多对多:关系配置
在表中要引入一张中间表
IQueryable的延迟执行
“IQueryable”的意思是“可查询的”,可以查询、但是没有执行查询,查询的执行被延迟了。
- 1、IQueryable只是代表一个“可以放到数据库服务器去执行的查询”,它没有立即执行,只是“可以被执行”而已。
- 2、对于IQueryable接口调用非终结方法的时候不会执行查询,而调用终结方法的时候则会立即执行查询。
- 3、终结方法:遍历、ToArray()、ToList()、Min()、Max()、Count()等;
- 4、非终结方法:GroupBy()、OrderBy()、Include()、Skip()、Take()等。
- 5、简单判断:一个方法的返回值类型如果是IQueryable类型,那么这个方法一般就是非终结方法,否则就是终结方法。
1、IQueryable代表一个对数据库中数据进行查询的一个逻辑,这个查询是一个延迟查询。我们可以调用非终结方法向IQueryable中添加查询逻辑,当执行终结方法的时候才真正生成SQL语句来执行查询。 2、可以实现以前要靠SQL拼接实现的动态查询逻辑。
EF Core
分页查询
- 1、Skip(3).Take(8) 最好显式指定排序规则
- Skip() 跳过多少条
- Take() 取多少条
- 2、需要知道满足条件的数据的总条数:用IQueryable的复用
LongCount和Count
- 3、页数:long pageCount = (long)Math.Ceiling(count * 1.0 / pageSize);
IQueryable底层是如何读取数据的
- 1、DataReader:分批从数据库服务器读取数据。内存占用小、 DB连接占用时间长;
- 2、DataTable:把所有数据都一次性从数据库服务器都加载到客户端内存中。内存占用大,节省DB连接。
IQueryable内部就是在调用DataReader优点:节省客户端内存。 缺点:如果处理的慢,会长时间占用连接。
如何一次性加载数据到内存?
1、一次性加载数据到内存:用IQueryable的ToArray()、ToArrayAsync()、ToList()、ToListAsync()等方法。
2、等ToArray()执行完毕,再断服务器试一下。
=======================================================
1、场景1:遍历IQueryable并且进行数据处理的过程很耗时。
2、场景2:如果方法需要返回查询结果,并且在方法里销毁DbContext的话,是不能返回IQueryable的。必须一次性加载返回。
3、场景3:多个IQueryable的遍历嵌套。很多数据库的ADO.NET Core Provider是不支持多个DataReader同时执行的。把连接字符串中的MultipleActiveResultSets=true删掉,其他数据库不支持这个。
=======================================================
EF Core的异步方法
- 1、SaveChanges()、SaveChangesAsync()
- 2、异步方法大部分是定义在Microsoft.EntityFrameworkCore这个命名空间下EntityFrameworkQueryableExtensions等类中的扩展方法,记得using。
- 3、AddAsync()、AddRangeAsync()、AllAsync()、AnyAsync、AverageAsync、ContainsAsync、CountAsync、FirstAsync、FirstOrDefaultAsync、ForEachAsync、LongCountAsync、MaxAsync、MinAsync、SingleAsync、SingleOrDefaultAsync、SumAsync等
IQueryable的这些异步的扩展方法都是“立即执行”方法,而GroupBy、OrderBy、Join、Where等“非立即执行”方法则没有对应的异步方法。
EF Core执行的原生SQL语句
- 1、尽管EF Core已经非常强大,但是仍然存在着无法被写成标准EF Core调用方法的SQL语句,少数情况下仍然需要写原生SQL。
- 2、可能无法跨数据库。
- 3、三种情况:非查询语句、实体查询、任意SQL查询。
有SQL注入漏洞?
1、字符串内插的方式会不会有SQL注入攻击漏洞吗?查看一下执行的SQL语句吧。 2、字符串内插如果赋值给string变量,就是字符串拼接;字符串内插如果赋值给FormattableString变量,编译器就会构造FormattableString 对象。打印FormattableString的成员试试看。 3、ExecuteSqlInterpolatedAsync()的参数是FormattableString类型。因此ExecuteSqlInterpolatedAsync会进行参数化SQL的处理。
EFCore如何知道实体数据变了
快照更改跟踪:首次跟踪一个实体的时候,EF Core 会创建这个实体的快照。执行SaveChanges()等方法时,EF Core将会把存储的快照中的值与实体的当前值进行比较。
实体的状态
- 已添加(Added):DbContext正在跟踪此实体,但数据库中尚不存在该实体。
- 未改变(Unchanged):DbContext正在跟踪此实体,该实体存在于数据库中,其属性值和从数据库中读取到的值一致,未发生改变
- 已修改(Modified):DbContext正在跟踪此实体,并存在于数据库中,并且其部分或全部属性值已修改。
- 已删除(Deleted):DbContext正在跟踪此实体,并存在于数据库中,但在下次调用 SaveChanges 时要从数据库中删除对应数据。
- 已分离(Detached):DbContext未跟踪该实体。
“已分离”和“未改变”的实体,SaveChanges()忽略; “已添加”的实体,SaveChanges() 插入数据库; “已修改”的实体,SaveChanges() 更新到数据库; “已删除”的实体,SaveChanges() 从数据库删除;
设置不跟踪:
1、如果通过DbContext查询出来的对象只是用来展示,不会发生状态改变,则可以使用AsNoTracking()来 “禁用跟踪”。
2、分别加AsNoTracking()和不加,分别查看一个对象修改后的EntityEntry 信息。
3、如果查询出来的对象不会被修改、删除等,那么查询时可以AsNoTracking(),就能降低内存占用。
全局查询筛选器
悲观并发控制
实现:
乐观并发控制
乐观并发控制:RowVersion
1、SQLServer数据库可以用一个byte[]类型的属性做并发令牌属性,然后使用IsRowVersion()把这个属性设置为RowVersion类型,这样这个属性对应的数据库列就会被设置为ROWVERSION类型。对于ROWVERSION类型的列,在每次插入或更新行时,数据库会自动为这一行的ROWVERSION类型的列其生成新值。
2、在SQLServer中,timestamp和rowversion是同一种类型的不同别名而已。
- 1、在MySQL等数据库中虽然也有类似的timestamp类型,但是由于timestamp类型的精度不够,并不适合在高并发的系统。
- 2、非SQLServer中,可以将并发令牌列的值更新为Guid的值。
- 3、修改其他属性值的同时,使用h1.RowVer = Guid.NewGuid()手动更新并发令牌属性的值。
总结:1、乐观并发控制能够避免悲观锁带来的性能、死锁等问题,因此推荐使用乐观并发控制而不是悲观锁。2、如果有一个确定的字段要被进行并发控制,那么使用IsConcurrencyToken()把这个字段设置为并发令牌即可;3、如果无法确定一个唯一的并发令牌列,那么就可以引入一个额外的属性设置为并发令牌,并且在每次更新数据的时候,手动更新这一列的值。如果用的是SQLServer数据库,那么也可以采用RowVersion列,这样就不用开发者手动来在每次更新数据的时候,手动更新并发令牌的值了。
EFCore-什么是表达式树?
- 1、表达式树(Expression Tree):树形数据结构表示代码,以表示逻辑运算,以便可以在运行时访问逻辑运算的结构。
- 2、Expression<TDelegate>类型
- 3、从Lambda表达式来生成表达式树:Expression<Func<Book, bool>> e1 = b =>b.Price > 5;
- 作者:JanePoint
- 链接:http://github.com/fanzhineng/article/EF
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。