EF Core基础
最近又细看了EF,再次详细做个总结,常规用法和一点点高级用法,应该可以解决百分之就是的问题
EFCore是什么
EFCore是微软自己的ORM框架,类比JAVA的JDBC。就是为了程序员不用操作复杂的sql,可以通过简单的操作完成对数据库的增删改查。
使用
EF使用首先写上下文类,继承DbContext
,重写OnConfiguring
和OnModelCreating
,写DbSet属性
OnConfiguring
连接字符串
OnModelCreating
包括字段类型限制和导航属性等
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
}
CRUD
实体类配置
EF Core采用了“约定大于配置”的设计原则,也就是说EF Core会默认按照约定根据实体类以及DbContext的定义来实现和数据库表的映射配置,除非用户显式地指定了配置规则。
配置类
使用:
- 配置类继承 IEntityTypeConfiguration
1 class BookEntityConfig : IEntityTypeConfiguration<Book>
2 {
3 public void Configure(EntityTypeBuilder<Book> builder)
4 {
5 builder.ToTable("T_Books");
6 builder.Property(e => e.Title).HasMaxLength(50).IsRequired();
7 builder.Property(e => e.AuthorName).HasMaxLength(20).IsRequired();
8 }
9 }
Data Annotation配置
直接在实体类的属性上加注解
Fluent API配置
如上builder.Property(e => e.Title).HasMaxLength(50).IsRequired();
这种编写
注意
- 推荐Fluent API配置
- 两种配置都用,Fluent API配置优先级高
关系配置
一对多、一对一、多对多
EF Core中实体类之间关系的配置采用如下的模式:HasOne(Teacher).WithMany(Student);一个老师有多个学生
关联数据的获取
Include,在使用关联表时需要使用,否则关联表的字段为null
关系的外键属性的配置
在关系配置中通过HasForeignKey()指定这个属性为外键可以不使用Include
单项导航属性
有时候我们不方便声明双向导航。比如在大部分系统中,基础的“用户”实体类会被非常多的其他实体类引用,这种单向导航属性的配置其实很简单,只要在WithMany方法中不指定属性即可
数据迁移
不建议使用,只有在数据库已经建立好,使用
scaffold
命令反向工程建立实体类,建立完成还需要一定修改
迁移文件
- XXX.cs:记录的是和具体数据库无关的抽象模型
- XXX.Designer.cs:记录的是和具体数据库相关的代码
查看EFCore生成的SQL语句
我们只要在上下文的OnConfiguring方法中调用optionsBuilder类的LogTo方法,传递一个参数为String的委托即可。当相关日志输出的时候,对应的委托就会被执行
原理
IQueryable与IEnumerable
Enumerable类中定义的供普通集合用的Where等方法都是“客户端评估”,Queryable中定义的Where方法都是“服务的评估”
总结:
在使用EF Core的时候,能一次性筛选出的数据尽量都一条sql搞定,而不是取出再筛选,也就是尽量使用“服务端评估”
例子:
新手(其实是我)在操作多个模糊查询时,会用foreach查询多次,实际完全可以使用Contain
和LINQ
的方法一次性取
IQueryable的延迟执行
对于IQueryable接口,调用“非立即执行”方法的时候不会执行查询,而调用“立即执行”方法的时候则会立即执行查询。
好处:
可以重复使用IQueryable的查询
判断方法:
一个方法是否是立即执行方法的简单方式是:一个方法的返回值类型如果是IQueryable类型,这个方法一般就是非立即执行方法,否则这个方法就是立即执行方法。
注:IQueryable是一个待查询的逻辑,因此它是可以被重复使用的
IQueryable的底层运行
IQueryable是用类似DataReader的方式读取查询结果的。DataReader会分批从数据库服务器读取数据。
优点是客户端内存占用小,缺点是如果遍历读取数据并进行处理的过程缓慢的话,会导致程序占用数据库连接的时间较长,从而降低数据库服务器的并发连接能力。因此,在遍历IQueryable的过程中,它需要占用一个数据库连接。
EF优化
AsNoTracking
如果开发人员能够确认通过上下文查询出来的对象只是用来展示,不会发生状态改变,那么可以使用AsNoTracking方法告诉IQueryable在查询的时候“禁用跟踪”
1 Book[] books = ctx.Books.AsNoTracking().Take(3).ToArray();
2 Book b1 = books[0];
3 b1.Title = "abc";
4 EntityEntry entry1 = ctx.Entry(b1);
5 Console.WriteLine(entry1.State);
上面代码的执行结果是“Detached”,也就说使用AsNoTracking查询出来的实体类是不被上下文跟踪的。
实体类状态跟踪
EF使用快照的方法跟踪实体的变化,也就是复制一份快照,当save的时候会查看该状态来判断是否更改数据,ctx.Entry(b1).State
查看该记录的状态
并发控制
EF Core内置了使用并发令牌列实现的乐观并发控制,并发令牌列通常就是被并发操作影响的列。
例子:
我们可以把Owner列用作并发令牌列。在更新Owner列的时候,我们把Owner列更新前的值也放入Update语句的条件中,SQL语句如下:Update T_Houses set Owner=新值where Id=1 and Owner=旧值。
使用:
EF Core中,我们只要把被并发修改的属性使用IsConcurrencyToken设置为并发令牌即可。
表达式树
表达式树(expression tree)是用树形数据结构来表示代码逻辑运算的技术,它让我们可以在运行时访问逻辑运算的结构。表达式树在.NET中对应Expression <> 类型。
Tip:华而不实,很多都可以用LINQ来代替,不推荐使用,不便于更新迭代。一般只有在编写不特定于某个实体类的通用框架的时候,由于无法在编译期确定要操作的类名、属性等,才需要编写动态构建表达式树的代码,否则为了提高代码的可读性和可维护性,我们要尽量避免动态构建表达式树。
通过代码动态构建表达式树
- 安装NuGet包ExpressionTreeToString
- 在代码中添加对ExpressionTreeToString命名空间的引用
- 我们就可以在Expression类型上调用ToString扩展方法来输出表达式树结构的字符串了
- ExpressionTreeToString提供的ToString(“Object notation”,“C#”)方法只是输出一个用C#语法描述表达式树的结构及每个节点的字符串,但是这个字符串并不是可以直接运行的C#代码。
- 我们可以用C#的using static方法来静态引入Expression类
- 这样上面的代码就几乎可以直接放到C#代码中编译通过了
ASP.NET Core中EF Core的使用
使用
context
只编写OnModelCreating
方法,而OnConfig
编写在Program.cs
中Program.cs
中添加builder.Services.AddDbContext<MyDbContext>(opt=>{ var conStr = builder.Configuration.GetConnectionString("dEFAULT"); opt.UseMysql(conStr) });