参考:.NET 6教程,.Net Core 2022视频教程,杨中科主讲_哔哩哔哩_bilibili
.NET Core 之 七 EF Core(一)
一、
什么是ORM,用于关系型数据库和C#对象之间双向转换,让开发者用对象操作的形式操作关系数据库,比如Linq转sql在数据库中执行,类对应数据表
有哪些ORM:EF Core、Dapper、Sqlsugar、FreeSql等
EF Core与其他ORM比较,和Dapper的比较;
EF Cores 模型驱动,Dapper是数据库驱动
项目选择技术框架
EF Core与EF比较,DB First、Model First、Code First,推荐使用Code First
二、
用什么数据库
开发环境搭建
1、经典步骤:建实体类、建配置类、建DbContext;Migration生成数据库,编写调用EF Core的业务代码
2、建实体类
3、NuGet 安装 Microsoft.EntityFrameworkCore.SqlServer
4、创建实现了 IEntityTypeConfiguration接口的实体配置类,配置实体类和数据库表的对应关系
5、创建继承自 DbContext的类,重写 OnConfiguring和OnModelCreating方法
Migration数据库迁移
面向对象的ORM开发中,数据库不是程序员手动创建的,而是由Migration工具生成的,根据对象定义的变化,自动更新数据库中的表以及表结构的操作,叫做Migration(迁移)
NuGet 安装 Microsoft.EntityFrameworkCore.Tools
在“程序包管理器控制台”中执行命令
Add-Migration 【有意义的名字】 生成Migration文件
再执行 update-database 在数据库中生成表
修改类结构后,再执行 Add-Migration和update-database,更新数据库中的表
对表字段做限制,Fluent API
builder.Property(e=>e.Title).HasMaxLength(50).IsRequired()
最大长度为50 不能为空
配置类可以使用约定,不建配置类
三、Entity Framework CURD操作
插入数据
查询数据 Linq,EF Core会把Linq转成sql
修改数据,删除数据 先查询再修改或删除,再SaveChangesAsync()
EF Core目前没有高效的批量修改和删除功能
批量修改 删除,使用杨中科老师的开源库:Zack.EFCore.Batch
四、EF Core 实体配置
常用规则
1、表名采用DbContext中对应的DbSet的属性名
2、数据表列的名字采用实体类属性的名字,列的数据类型采用和实体属性类型最兼容的类型
3、数据表列的可空性取决于对应实体类属性的可空性
4、名字为Id的属性为主键,如果主键为short,int或者long类型,则默认采用自增字段,如果主键为Guid类型,则默认采用默认的Guid生成机制生成主键值
两种配置方式
1、Data Annotation,直接在类属性上写[ ],简单但不解耦
2、Fluent API,创建config类,复杂但解耦,灵活
可以混用,但不建议混用,有前后优先级,推荐使用Fluent API
技术选型,适度且合理复杂,人生经验,学复杂的值钱
Fluent API
1、视图与实体类映射:modelBuilder.Entity<Blog>().ToView("blogsView");
2、排除属性映射(不推荐使用)
modelBuilder.Entity<Blog>().Ignore(b=>b.Name2);
3、配置列名:
modelBuilder.Entity<Blog>().Property(b=>
b.BlogId).HasColumnName("blog_id");
4、配置列数据类型
builder.Property(e=e.Title).HasColumnType("varchar(200)")
//和HasMaxLength(200) 生成数据类型一样
char varchar nvarchar(推荐)
固定长度 存英文 存中英文
5、配置主键
默认把名字为Id或者“实体类型+Id”的属性作为主键,可以用HasKey()来配置其他属性作为主键。modelBuilder.Entity<Student>().HasKey(c=>c.Number);
支持复合主键,但是不建议使用
6、生成列的值(计算列-推荐,触发器等,数据库自动生成的列)
modelBuilder.Entity<Student>.Property(b=>
b.Number).ValueGeneratedOnAdd();
7、可以用HasDefaultValue()为属性设定默认值
modelBuilder.Entity<Student>().Property(b=>
b.Age).HasDefaultValue(6);
8、索引
modelBuilder.Entity<Blog>().HasIndex(b=>b.Url);
复合索引
moderlBuilder.Entity<Person>().HasIndex(p=>new {p.FirstName,p.LastName});
9、...
用EF Core太多高级特性的时候需谨慎,尽量不要和业务逻辑混合在一起,以免“不能自拔”,比如:Ignore,Shodow,Table Splitting等
五、
Fluent API 属性的不同写法,推荐使用lambda表达式的写法,可以检查错误,自动填充
遵从C#语法
链式编程代码风格,推荐;
Data Annotation和Fluent API 官方文件
.NET Core 之 七 EF Core(二)
杨中科老师视频
.NET 5教程,.Net Core 2021视频教程,杨中科主讲_哔哩哔哩_bilibili
一、主键不是小事,数据库设计、优化
EF Core 支持多种主键生成策略:自动增长,Guid,Hi/Lo算法等
自动增长,数据库自动赋值
优点:简单,缺点:数据库迁移以及分布式系统中比较麻烦,并发性差;
Savechanges后会自动把主键的值更新到Id属性
Guid算法,适用于分布式系统,EF Core赋值
优点:简单、高并发、全局唯一,缺点:磁盘空间占用大
Guid值不连续,使用Guid类型做主键的时候,不能把主键设置为聚集索引,因为聚集索引是按照顺序保存主键的,因此用Guid做主键并设置为聚集索引性能差,比如:mysql的InnoDB引擎中主键是强制使用聚集索引的。在sql server等中,不要把Guid主键设置为聚集索引,在mysql中不要用Guid做主键。
演示Guid存储,推荐手动赋值,Guid.NewGuid()
其他方案
1、混合自增和Guid(非复合主键),用自增列做物理主键,而用Guid列做逻辑上的主键,把自增列设置为表的主键,而在业务上查询数据时把Guid当作主键使用,在和其他表关联以及和外部系统通讯的时候都是用Guid列,不仅保证了性能,而且利用了Guid的优点,而且减轻了主键自增性导致主键值可被预测带来的安全性问题。
2、Hi/Lo算法
优点:高效,缺点,HiLo算法不是EF Core的标准,不支持所有数据库,支持SQL Server;
用法自己看文档;
二、深入研究 Migrations
向上迁移(Up),向下迁移(Down)退回到旧的迁移
分析Migrations下的代码,分析Up、Down方法;
三、Migration 其他命令
1、update-database xxx 到某个迁移节点
2、Remove-Migration 删除最后一次迁移脚本
先要update-database回退到某个节点(删除数据库表),再执行Remove-Migration删除最后一次的
3、Script-Migration 生成迁移sql脚本
为什么要生成sql脚本,用于生产环境数据库更新
Script-Migration D F 生成版本D到版本F的sql脚本
Script-Migration D 生成版本D到最新版本的sql脚本
四、反向工程
根据数据库表来反向生成实体类,仅推荐用于已有数据库表时使用;
DB First,Mode First(图形),Code First
代码演示
1、保证项目中已经安装了EF Core和EF Core.Tools的依赖包
2、nuget包管理器输入命令 sql server 使用
Scaffold-DbContext '数据库连接字符串' Microsoft.EntityFrameworkCore.SqlServer
oracle 使用
Scaffold-DbContext '连接字符串' Oracle.EntityFrameworkCore -ContextDir ContextData -OutputDir ModelsData
3、可以使用
Scaffold-DbContext '数据库连接字符串' Microsoft.EntityFramework.Core.SqlServer -Force 命令再次覆盖重新生成代码,会修改值钱的代码,不推荐使用;
五、EF Core底层如何操作数据库
1、为什么要了解EF Core底层原理,更好解决问题,充实能力
框架是帮助程序员简化工作的,不是把程序员变成傻瓜的
2、ADO.NET Core
3、
4、Sql Server Profiler 工具查看sql
EF Core是一个把C#代码转换为sql语句的框架
六、EF Core有哪些做不到的事情
代码演示
C#千变万化,总有一些语法无法被转换成sql,编译成功运行报错
原理:
AST,编译原理:抽象语法树
七、通过代码查看EF Core生成的sql语句
方法1:标准日志,
optionsBuilder.UseLoggerFactory(MyLoggerFactory);
代码演示
方法2:简单日志
optionsBuilder.LogTo(msg=>{console.writeLine(msg)});
输出了EF Core整个过程
过滤掉不需要的信息
方法3:ToQueryString() IQueryable扩展方法,只能用于查询操作
总结:测试性代码,用简单日志;正式上线或排查故障,用标准日志;开发阶段,从繁杂的查询操作中立即看到sql,用ToQueryString();
八、同样的Linq被翻译为不同的sql语句
EF Core 迁移脚本和数据库相关
通过给Add-Migration命令添加“-OutputDir”参数的形式来在同一个项目中为不同的数据库生成不同的迁移脚本
演示Linq翻译成的sql
Mysql项目
1、EF Provider的选择
2、NuGet安装 pomelo.EntityFrameworkCore.Mysql
3、 optionsBuiler.useMysql('server=localhost;user=root;password=root;database=demo1',new MySqlServerVersion(new Version(8,0,19)));官方文档
演示生成Mysql Migration脚本
Linq生成的mysql sql语句
postgraSQL项目,开源
NuGet安装 Npgsql.EntityFrameworkCore.PostgraSql 准官方
optionsBuilder.UseNpgsql("Host=localhost;Database=demo1;Username=postgres;Password=123456"); 官方文档
演示生成postgraSql Migration脚本
Linq生成的PostgreSql sql脚本
演示相同Linq在不同数据库时,不能翻译成对应的sql语句
.NET Core 之 七 EF Core(三)
杨中科老师视频
.NET 5教程,.Net Core 2021视频教程,杨中科主讲_哔哩哔哩_bilibili
一、EF Core一对多关系配置
1、所谓“关系数据库”
2、复习,数据库表之间的关系,“一对一” “一对多” “多对多”
多对多表关系通过第三方关系表,保持对应关系
3、三部曲:实体类中关系属性;Fluent API关系配置;使用关系操作
一对多,实体类
Article public List<Comment> comments {get;set;} = new List<Comment>();
List推荐默认给空集合
Comment public Article Article{get;set;}
套路:HasXXX(...).WithXXX(...) 有XXX,反之带有XXX
一对多:HasOne(...).WithMany(...)
一对一:HasOne(...).WithOne(...)
多对多:HasMany(...).WithMany(...)
演示:建配置关系,插入数据
EF Core会“顺竿爬”,简化代码
二、
演示查询关联数据,把Article以及所有Comment查询出来
Article a=ctx.Article.Include(a=>a.Commentts).Single(a=>a.id==2);
Include:同时查询关联的对象
EF Core中目前没有一种方式让生成的sql采用那种join;
三、额外的外键字段,需求:只想获得ArticleId和Comment,不要Article其他字段
SQL优化,尽量不要select * 原则,采用Linq select 匿名类
在查询时,使用select可以省略写Include,但是SQL还是会join
步骤:
1、在实体类中显式声明一个外键属性
2、在关系配置中通过 HasForeignKey(c=>c.ArticleId) 指定这个属性为外键
3、除非必要,否则不用声明外键列,因为会引入重复
EF Core:大部分查询比绝大部分程序员写出来的SQL性能都要高,有少部分SQL语句性能可能不尽如人意,但是也影响不大,特殊的一些SQL语句可能影响性能瓶颈,咱们再需要特殊优化
四、EF Core 单向导航属性
导航属性:在自己类中可以访问其他类的属性,例如:Comments
双向导航属性的问题,基础表被多个其他表引用时麻烦
单向导航对应结构
HasOne<User>(u=>u.Requester).WithMany() WithMany不设置参数
选择
对于主从结构的“一对多”表关系,一般是声明双向导航属性,而对于其他的“一对多”表关系,如果表属于被很多表引用的基础表,则用单向导航属性,否则可以自由决定是否是双向导航属性;
五、关系配置在任何一方都可以 推荐配置在多的那一端
正反都一样 一对多,多对一
演示配置放在另一方配置中
HasOne<Article>(c=>c.Article).WithMany(a=>a.Comments)
同等于 HasMany<Commentt>(a=>a.Comments).WithOne(c=>c.Article)
推荐策略,考虑到有单向导航属性的可能,我们一般用HasOne().WithMany(),关系配置到多的那一端;
六、自引用的组织结构树 类似字典表结构,ParentId是本身表的Id
代码演示, 利用“顺竿爬”两种方式二选一
自己研究下,这个地方优点绕,测试一下代码,插入数据
递归查询数据
new String('.',3) 指定字符重复若干次 new String('\t',3) 三个制表符
七、一对一
必须显式的在其中一个实体类中声明一个外键属性(在一对多中必要时才声明外键属性,一般不声明)
bulider.HasOne<Delivery>(o=>o.Delivery).WithOne(d=>d.order)
.HasForeignKey<Delivery>(d=>d.orderId);
外键属性在Deliver类中,配置外键在OrderConfig类中,是否可以调换位置呢??可以,配置在哪边都行
可以不用“顺竿爬”都存起来,靠谱;
八、多对多 EF Core5.0开始,才正式支持多对多
数据库中需要中间表
builder.HasMany<Teacher>(s=>s.Teachers).WithMany(t=>t.Students)
.UsingEntity(j=>j.ToTable("T_Students_Teachers"));
UsingEntity 设置中间表表名
代码演示,总结
九、基于关系的复杂查询,性能调优
eg:查找评论中含有“微软”两个子的所有文字(文字和评论是双向导航属性)
1、var items = ctx.Articles.Where(a=>a.Comments.Any(c=>c.Message.Contains("微软")));
2、var items = ctx.Comments.where(c=>c.Message.Contains("微软")).Select(c=>c.TheArticle).Distinct(); Distinct 去重
两种实现方式生成的sql不一样,可以对比调优
.NET Core 之 七 EF Core(四)
一、有了IEnumerable 还要IQueryable干什么
普通集合的版本(IEnumerable)是在内存中过滤(客户端评估),而IQueryable版本则是把查询操作翻译成SQL语句后给数据库服务器(服务器端评估)
代码演示 一般IQueryable性能更高
二、偶尔客户端评估性能更好 作用性能优化
Every disadvantage has its advantage
eg:服务端:ctx.Comment.Select(c=>new {Id=c.Id,Pre=c.Message.Substring(0,2)+"..."});
客户端:((IEnumerable<Comments>)ctx.Comments).Select(c=>new {Id=c.Id,
Pre=c.Message.Substring(0,2)+"..."});
eg2:逻辑太复杂无法被翻译成SQL时,只能采用客户端评估,具体问题具体分析;
三、IQueryable延迟执行
代码演示
什么时候会执行IQueryable的SQL?
1、IQueryable知识代表一个“可以放到数据库服务器去执行的查询”,它没有立即执行,只是“可以被执行”而已;
2、对于IQueryable接口调用非终结方法的时候不会执行,而调用终结方法的时候则会立即执行查询
3、终结方法:遍历,ToArray(),ToList(),Min(),Max(),Count()等;
4、非终结方法:GroupBy(),OrderBy(),Include(),Skip(),Take()等
5、简单判断:一个方法的返回值类型,如果是IQueryable类型,那么这个方法一般是非终结方法,否则就是终结方法
为什么会需要延迟执行?
1、可以在实际执行之前,分布构建IQueryable;
演示
2、应用场景,根据条件不同,构建不同的IQueryable查询
总结
四、IQueryable的复用
代码演示,类似 三 中的用法
五、分页查询
1、Skip(3).Take(8),最好显示指定排序规则
2、总条数,LongCount和Count
3、页数,long PageCount = (long) Matg.ceiling(conut * 1.0/PageSize)
六、IQueryable底层是如何读取数据的
ADO.NET
1、DataReader:分批从数据库服务器读取数据,内存占用小,DB连接占用时间长;
2、DataTable:把所有数据都一次性从数据库服务器加载到客户端内存中,内存占用大,节省DB连接
验证IQueryable 用什么方式加载数据,默认用DataReader方式
·1、快速造数据
insert into User(Id,Name,Age)
select Id,Name,Age from User
2、演示加载数据过程中停掉数据库服务,会报错
如何一次性加载数据到内存中
.ToArray(),ToArrayAsync(),ToList(),ToListtAsync()等方法;
何时需要一次性加载所有数据 性能优化
场景1:遍历IQueryable 并且进行数据处理的过程很耗时;
场景2:如果方法需要返回查询结果,并且在方法里销毁DbContext的话,是不能返回IQueryable的,必须一次性加载返回;
场景3:多个IQueryable 的遍历嵌套,很多数据库的ADO.NET Core.Provider 是不支持多个DataReader同时执行的,sql server中把连接字符串中的MultipleActionResultSets=true 删掉,其他数据库不支持这个
七、EF Core中的异步方法
为什么有的方法没有异步方法?
IQueryable的异步扩展方法都是“立即执行”方法(终结方法),而像GroupBy,OrderBy,Include,Where等“非立即执行”方法(非终结方法)则没有对应的异步方法
因为“非立即执行”方法并没有实际执行SQL语句,并不是消耗IO的操作;
如何异步遍历 IQueryable
方式1:ToListAsync(),ToArrayAsync();结果集不要太大;
方式2:await foreach(Book in ctx.Books.AsAsyncEnumerable())
AsAsyncEnumerable,把IQueryable转化为异步的Enumerable在await遍历
不过,一般没必要这么做,性能优化方案
.NET Core 之 七 EF Core(五)
一、EF Core执行非查询原生sql语句
为什么要写原生sql语句
执行非查询sql语句
内插值语法:$"我是{name},我的年龄是{age}"
多行@
ctx.Database.ExecateSqlInterpolatedAsync($"inster into...")
有sql注入漏洞吗? 没有 sql注入最好的解决方案是参数化sql处理
1、字符串内插值的方式不会有sql注入攻击漏洞;
2、字符串内插如果赋值给string变量,就是字符串拼接;字符串内插值如果赋值给FormattableString类型变量,编译器就会构造FormattableString对象,属性Formar和GetArguments()方法
3、ExecuteSqlInterPolatedAsync()的参数FormattableString类型,因此ExecutSqlInterPolatedAsync会进行参数化sql处理
4、除了ExecuteSqlInterPolated(),ExecuteSqlInterPolatedAsync()还有ExecuteSqlRaw(),ExecuSqlRawAsync()也可以执行原生sql语句,但需要开发人员自己处理查询参数,用不好容易造成sql注入风险,以前旧版本用,新版本推荐用内插值方法。
二、执行实体相关查询原生sql语句
如果要执行的原生sql是一个查询语句,并且查询结果也能对应一个实体,就可以调用对应实体的DbSet的FormSqlInterPolated()方法来执行一个查询sql语句,同样使用字符串内插来传递参数
SQL select newid() 可用作随机排序
like时需要参数传入"%中%",不能把%直接放入字符串中
SQL 子查询中不能使用order By
把只能用原生sql语句写的逻辑用FormSqlInterPolated()去执行,然后把分页,分组,二次过滤,排序,Include等其他逻辑尽可能仍然使用EFCore的标准操作去实现
局限性
1、sql查询必须返回实体类型对应数据库表的所有列;
2、结果集中的列名必须与属性映射的列名称匹配;
3、只能单表查询,不能使join语句进行关联查询,但是可以在查询后面使用Include()来进行关联数据的获取;
三、EFCore执行任意原生sql查询语句
什么时候需要ADO.NET
不推荐使用视图,存储过程
执行任意sql
ADO.NET 演示,操作太复杂不推荐
Dapper框架
演示,1、建对应DTO对象 2、Query方法
总结
四、EFCore如何知道实体数据变了
演示,SaveChanges时奇怪的地方,与数据库不一致的才会执行修改
快照更改跟踪,概念
实体的状态:已添加(Added),未改变(Unchanged),
已修改(Modified),已删除(Deleted),已分离/未跟踪(Datached)
Savechanges的操作
EntityEntry
1、使用DbContext的Entry()方法来获得实体在EFCore中的跟踪信息对象EntityEntry,EntityEntry类的State属性代表实体的状态,通过DebugView.LongView属性可以看到实体的变化信息
2、代码演示
总结:DbContext会根据跟踪的实体的状态,在SaveChanges()的时候根据实体状态的不同,生成update,delete,inster等sql语句,来把内存中实体的变化更新到数据库中。这就是SaveChanges的原理
五、EFCore优化之AsNoTracking
如果查询出来的对象不会被修改,删除等吗,那么查询时可以用AsNoTracking(),就能降低内存占用;不需要跟踪状态,不需要快照
代码演示
六、EFCore实体状态跟踪的妙用(不推荐使用)
修改和删除只执行一句sql语句,不用执行查询sql,可以性能优化,但代码可读性不强,难维护,提升微乎其微,弊大于利,不推荐使用;
七、EFCor数据的批量删除、更新、插入
SQLBulkCopy 可以一次性把很多数据插入到数据库中
演示,EFCore批量修改、删除、插如实体n条sql会执行
为什么不用sql实现
a、原生sql语句需要把表名、列名等硬编码到sql语句中,不符合模型驱动,分层隔离等思想,程序员直接面对数据库表,无法利用EFCore强类型的特性,如果模型发生改变,必须手动变更sql语句;
b、无法利用EFCore强大的sql翻译机制来屏蔽不同底层数据库的差异
c、EFCore官方迟迟未支持的原因
与EFCore实体状态跟踪冲突,会导致状态混乱
我的开源实现 Zack.EFCore.Batch 包
批量删除、更新、插入 使用方法,官方文档
演示
八、全局查询筛选器
EFCore会自动将这个查询筛选器应用于涉及这个实体类型的所有Linq查询
场景:软删除,多租户(eg:商城系统商家入驻,每条记录都带有商户ID)
软删除概念,代码演示
builder.HasQueryFilter(a=>a.IsDeleted == false);
忽略:ctx.Books.IgnoreQueryFilters().Where(b=>b.Age>10).ToArray();
全局筛选器的性能陷阱:数据量大时,可能会造成全表扫描,根据情况优化系统,如:聚集索引