5.1EF Core原理

简介: 对普通集合使用where等方法查询出来的返回值为IEnumerable类型但是对DbSet使用用where等方法出查询出来的返回值为IQueryable类型

EF Core原理

IEnumerable和IQueryable

对普通集合使用where等方法查询出来的返回值为IEnumerable类型

但是对DbSet使用用where等方法出查询出来的返回值为IQueryable类型

IQueryable继承自IEnumberable,对于普通集合,Where方法会在内存中对数据进行过滤,但是EF Core如果把数据表中的所有元素加载到内存然后再过滤的话,会导致占用内存过大。所以,使用IQueryable是把where方法转换成SQL语句,使得可以在数据库中进行过滤查询。

IQueryable延迟执行

对于IQueryable接口,调用“非立即执行”方法不会立刻执行查询,调用“立即执行”方法才会立刻执行查询

立即执行方法 非立即执行方法
遍历IQueryable方法 Where
ToArray GroupBy
ToList OrderBy
Min Include
Max Skip
Count Take

判断是否为立即执行方法:如果返回值类型是IQueryable则是非立即执行方法

延迟执行的好处是可以拼接IQueryable和复用

voidQueryBooks(stringsearchWords, boolsearchAll, boolorderByPrice, doubleupperPrice)

{

   usingTestDbContextctx=newTestDbContext();

   IQueryable<Book>books=ctx.Books.Where(b=>b.Price<=upperPrice);//此时并没有执行sql语句

   if (searchAll)//匹配书名或、作者名

   {

       books=books.Where(b=>b.Title.Contains(searchWords) ||b.AuthorName.Contains(searchWords));//复用books

   }

   else//只匹配书名

   {

       books=books.Where(b=>b.Title.Contains(searchWords));

   }

   if (orderByPrice)//按照价格排序

   {

       books=books.OrderBy(b=>b.Price);

   }

   //此时才调用sql语句

   foreach (Bookbinbooks)

   {

       Console.WriteLine($"{b.Id},{b.Title},{b.Price},{b.AuthorName}");

   }

}

分页查询

voidOutputPage(intpageIndex, intpageSize)

{

   usingTestDbContextctx=newTestDbContext();

   IQueryable<Book>books=ctx.Books.Where(b=>!b.Title.Contains("张三"));

   longcount=books.LongCount();//总条数

   longpageCount= (long)Math.Ceiling(count*1.0/pageSize);//页数

   Console.WriteLine("页数:"+pageCount);

   //skip(n)跳过n条数据 Take(n)取最多n条数据

   varpagedBooks=books.Skip((pageIndex-1) *pageSize).Take(pageSize);

   foreach (varbinpagedBooks)

   {

       Console.WriteLine(b.Id+","+b.Title);

   }

}

IQueryable底层执行

ADO.NET中的DataReader中是分批从数据库中读取数据,而DataTable则是一次性读取数据

在遍历IQueryable时,是使用DataReader的方式读取数据,这样会一直占用一个数据库的连接。如果使用ToArray等方法,则使用了DataTable方式。除非遍历IQueryable并进行数据处理的过程很耗时,否则一般不需要一次性读取所有的结果,除了下面两种场景:

  • 场景1:如果方法需要返回查询结果,并在方法内销毁上下文。

//错误,因为上下文在方法执行完成就销毁了

IQueryable<Book>QueryBooks()

{

   usingTestDbContextctx=newTestDbContext();

   returnctx.Books.Where(b=>b.Id>5);

}

//正确做法

IEnumerable<Book>QueryBooks()

{

   using (TestDbContextctx=newTestDbContext())

   {

       returnctx.Books.Where(b=>b.Id>5).ToArray();

   }

}

  • 场景2:IQueryable嵌套

DataReader方式,有些数据库不支持多个DataReader同时执行

//错误,有两个DataReader

varbooks=ctx.Books.Where(b=>b.Id>1);

foreach(varbinbooks)

{

   foreach(varainctx.Authors)

   {

       Console.WriteLine(a.Id);

   }

}

//可以将某个IQueryable使用ToList()加载到内存中来解决

EF Core异步方法

大部分异步方法定义在Microsoft.EntityFrameworkCore命名空间下

只有立即执行方法才有对应的异步方法

执行原生的SQL语句

  1. 执行SQL非查询语句

dbCtx.Database.ExecuteSqlInterpolated或者异步的ctx.Database.ExecuteSqlInterpolatedAsync方法执行SQL语句

usingTestDbContextctx=newTestDbContext();

Console.WriteLine("请输入最低价格");

doubleprice=double.Parse(Console.ReadLine());

Console.WriteLine("请输入姓名");

stringaName=Console.ReadLine();

introws=awaitctx.Database.ExecuteSqlInterpolatedAsync(@$"

   insertintoT_Books (Title,PubTime,Price,AuthorName)

   selectTitle, PubTime, Price,{aName} fromT_BookswherePrice>{price}");

           

//参数是FormattableString类型,会自动把{aName}、{price}翻译为@p0,@p1

  1. 执行实体类SQL查询语句

如果SQL是一个查询语句,并且查询结果能对应一个实体类,则可以调用DbSet的FromSqlInterpolated执行sql语句

usingTestDbContextctx=newTestDbContext();

intyear=int.Parse(Console.ReadLine());

//参数是同样是FormattableString类型,会自动把{year}翻译为@p0

IQueryable<Book>books=ctx.Books.FromSqlInterpolated(@$"select * from T_Books

       whereDatePart(year,PubTime)>{year} orderbynewid()");

foreach (Bookbinbooks)

{

   Console.WriteLine(b.Title);

}

//返回值是IQueryable类型,可以复用

FromSqlInterpolated的局限下:

  • SQL查询必须返回实体类型对应数据库表的所有列
  • 查询结果集中的列明必须与属性映射到列明匹配
  • 只能进行单表查询,不能使用过Join进行关联查询,但是可以在查询后面使用Include方法进行查询

实体类的跟踪

EF Core采用“快照更改跟踪”实现实体类改变的检测,当执行SaveChanges方法的时候,把存储在快照中的值与实体类的当前值进行比较,已确定哪些属性被更改。

实体类有一下5个可能的状态:

  • 已添加。上下文正在跟踪实体类,但是还没有加到数据库表中
  • 未改变。上下文正在跟踪实体类,且实体类存在于数据库,其属性值和数据库中一致
  • 已修改。上下文正在跟踪实体类,且实体类存在于数据库,某些属性值已被修改
  • 已删除。上下文正在跟踪实体类,且实体类存在于数据库,但下次调用SaveChanges时会在数据库中删除
  • 分离。上下文未跟踪实体类

对于不同状态的实体类,执行SaveChange,会有不同的操作。

  • 对于分离和未改变的实体类:直接忽略
  • 对于已添加的实体类:插入数据库
  • 对于已修改的实体类:修改后更新到数据库
  • 对于已删除的实体类:从数据库删除

usingTestDbContextctx=newTestDbContext();

Book[] books=ctx.Books.Take(3).ToArray();

Bookb1=books[0];

Bookb2=books[1];

Bookb3=books[2];

Bookb4=newBook { Title="零基础趣学C语言", AuthorName="杨中科" };

Bookb5=newBook { Title="百年孤独", AuthorName="马尔克斯" };

b1.Title="abc";

ctx.Remove(b3);

ctx.Add(b4);

EntityEntryentry1=ctx.Entry(b1);

EntityEntryentry2=ctx.Entry(b2);

EntityEntryentry3=ctx.Entry(b3);

EntityEntryentry4=ctx.Entry(b4);

EntityEntryentry5=ctx.Entry(b5);

Console.WriteLine("b1.State:"+entry1.State);//更改

Console.WriteLine("b1.DebugView:"+entry1.DebugView.LongView);//可以看出旧值和新值

Console.WriteLine("b2.State:"+entry2.State);//未改变

Console.WriteLine("b3.State:"+entry3.State);//删除

Console.WriteLine("b4.State:"+entry4.State);//增加

Console.WriteLine("b5.State:"+entry5.State);//未跟踪

相关文章
|
数据库 C++
.NET Core 使用 EF 出错的解决方法
.NET Core 使用 EF 出错的解决方法
483 0
.NET Core 使用 EF 出错的解决方法
|
4月前
|
数据库
分享一个 .NET EF 6 扩展 Where 的方法
分享一个 .NET EF 6 扩展 Where 的方法
|
关系型数据库 MySQL 数据库
EF Core反向工程
EF Core反向工程,数据库创建表使用命令生成上下文
101 0
|
SQL 存储 XML
|
关系型数据库 MySQL Linux
.Net Core 的WebApi项目使用mysql的EF CodeFirst模式
.Net Core 的WebApi项目使用mysql的EF CodeFirst模式
232 0
.Net Core 的WebApi项目使用mysql的EF CodeFirst模式
|
开发框架 算法 .NET
4.1EF Core
EF Core是微软官方的ORM框架,ORM即对象关系映射,也就是我们可以直接操作C#中的对象就可以完成数据库的操作。
|
开发框架 .NET 数据库连接
7.3EF Core与ASP.NET Core集成
7.3EF Core与ASP.NET Core集成
|
SQL 开发框架 .NET
5.2 EF Core性能优化
上文讲了实体类的跟踪以便执行SaveChanges操作。但是如果是查询操作,则实体类便不需要进行跟踪。
|
关系型数据库 MySQL 编译器
在 EF Core 7 中实现强类型 ID
本文主要介绍 DDD 中的强类型 ID 的概念,及其在 EF 7 中的实现,以及使用 LessCode.EFCore.StronglyTypedId 这种更简易的上手方式。
245 0
在 EF Core 7 中实现强类型 ID
|
缓存 关系型数据库 MySQL
C#-EF Core使用MySQL数据库
Entity Framework Core (EF Core)是微软推荐的基于.NET Core 的应用程序数据访问技术。开源,轻量级,可扩展并且支持跨平台开发。EF Core是一种对象关系映射器(ORM),通过应用程序实体对象和关系数据库中的数据的映射,使得开发人员能够以面向对象的方式处理数据。
627 0