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