• 5.1EF Core原理


    EF Core原理

    IEnumerable和IQueryable

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

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

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

    IQueryable延迟执行

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

    立即执行方法非立即执行方法
    遍历IQueryable方法Where
    ToArrayGroupBy
    ToListOrderBy
    MinInclude
    MaxSkip
    CountTake

    *判断是否为立即执行方法:*如果返回值类型是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}");
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    分页查询

    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 场景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()加载到内存中来解决
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    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类型,可以复用
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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);//未跟踪
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  • 相关阅读:
    统计gitlab代码提交情况
    4.7拆解复杂问题-实现计算器
    【AnolisOS 8.x】切换运行级别和设置默认运行级别
    Java对象的四种引用
    2023.09.30使用golang1.18编译Hel10-Web/Databasetools的windows版
    docker创建一个kafka集群
    (WebFlux)003、多数据源R2dbc事务失效分析
    使用id限定优化mysql分页查询limit偏移量大问题
    【BOOST C++ 14 消息编程】(2) 异步数据交换
    面向对象设计有六大原则
  • 原文地址:https://blog.csdn.net/weixin_44064908/article/details/126329121