5.1EF Core原理

举报
步步为营 发表于 2023/02/16 10:39:43 2023/02/16
【摘要】 对普通集合使用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和复用

void QueryBooks(string searchWords, bool searchAll, bool orderByPrice, double upperPrice)
{
	using TestDbContext ctx = new TestDbContext();
	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 (Book b in books)
	{
		Console.WriteLine($"{b.Id},{b.Title},{b.Price},{b.AuthorName}");
	}
}

分页查询

void OutputPage(int pageIndex, int pageSize)
{
	using TestDbContext ctx = new TestDbContext();
	IQueryable<Book> books = ctx.Books.Where(b => !b.Title.Contains("张三"));
	long count = books.LongCount();//总条数
	long pageCount = (long)Math.Ceiling(count * 1.0 / pageSize);//页数
	Console.WriteLine("页数:" + pageCount);
    //skip(n)跳过n条数据 Take(n)取最多n条数据
	var pagedBooks = books.Skip((pageIndex - 1) * pageSize).Take(pageSize);
	foreach (var b in pagedBooks)
	{
		Console.WriteLine(b.Id + "," + b.Title);
	}
}

IQueryable底层执行

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

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

  • 场景1:如果方法需要返回查询结果,并在方法内销毁上下文。
//错误,因为上下文在方法执行完成就销毁了
IQueryable<Book> QueryBooks()
{
	using TestDbContext ctx = new TestDbContext();
	return ctx.Books.Where(b => b.Id > 5);
}
//正确做法
IEnumerable<Book> QueryBooks()
{
	using (TestDbContext ctx = new TestDbContext())
	{
		return ctx.Books.Where(b => b.Id > 5).ToArray();
	}
}
  • 场景2:IQueryable嵌套

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

//错误,有两个DataReader
var books = ctx.Books.Where(b=>b.Id>1);
foreach(var b in books)
{
    foreach(var a in ctx.Authors)
    {
        Console.WriteLine(a.Id);
    }
}
//可以将某个IQueryable使用ToList()加载到内存中来解决

EF Core异步方法

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

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

执行原生的SQL语句

  1. 执行SQL非查询语句

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

using TestDbContext ctx = new TestDbContext();
Console.WriteLine("请输入最低价格");
double price = double.Parse(Console.ReadLine());
Console.WriteLine("请输入姓名");
string aName = Console.ReadLine();
int rows = await ctx.Database.ExecuteSqlInterpolatedAsync(@$"
	insert into T_Books (Title,PubTime,Price,AuthorName)
	select Title, PubTime, Price,{aName} from T_Books where Price>{price}");
            
//参数是FormattableString类型,会自动把{aName}、{price}翻译为@p0,@p1
  1. 执行实体类SQL查询语句

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

using TestDbContext ctx = new TestDbContext();
int year = int.Parse(Console.ReadLine());
//参数是同样是FormattableString类型,会自动把{year}翻译为@p0
IQueryable<Book> books = ctx.Books.FromSqlInterpolated(@$"select * from T_Books
		where DatePart(year,PubTime)>{year} order by newid()");
foreach (Book b in books)
{
	Console.WriteLine(b.Title);
}
//返回值是IQueryable类型,可以复用

FromSqlInterpolated的局限下:

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

实体类的跟踪

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

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

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

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

  • 对于分离和未改变的实体类:直接忽略
  • 对于已添加的实体类:插入数据库
  • 对于已修改的实体类:修改后更新到数据库
  • 对于已删除的实体类:从数据库删除
using TestDbContext ctx = new TestDbContext();
Book[] books = ctx.Books.Take(3).ToArray();
Book b1 = books[0];
Book b2 = books[1];
Book b3 = books[2];
Book b4 = new Book { Title = "零基础趣学C语言", AuthorName = "杨中科" };
Book b5 = new Book { Title = "百年孤独", AuthorName = "马尔克斯" };
b1.Title = "abc";
ctx.Remove(b3);
ctx.Add(b4);
EntityEntry entry1 = ctx.Entry(b1);
EntityEntry entry2 = ctx.Entry(b2);
EntityEntry entry3 = ctx.Entry(b3);
EntityEntry entry4 = ctx.Entry(b4);
EntityEntry entry5 = 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);//未跟踪
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。