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语句
- 执行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
- 执行实体类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);//未跟踪